wowsql 2.0.0
wowsql: ^2.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/Dart SDK #
Official Flutter/Dart SDK for WowSQL — a MySQL Backend-as-a-Service platform with built-in authentication, S3-compatible storage, and schema management.
Installation #
Add wowsql to your pubspec.yaml:
dependencies:
wowsql: ^2.0.0
Then run:
flutter pub get
Or install via command line:
dart pub add wowsql
Quick Start #
Database #
import 'package:wowsql/wowsql.dart';
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'your-api-key',
);
// Fetch rows
final response = await client.table('users').select('*').get();
print(response.data);
// Insert a row
final created = await client.table('products').create({
'name': 'Widget',
'price': 29.99,
});
print(created.insertedId);
client.close();
Storage #
import 'package:wowsql/wowsql.dart';
import 'dart:typed_data';
final storage = WOWSQLStorage(
projectUrl: 'your-project-url',
apiKey: 'your-api-key',
);
// Create a bucket
await storage.createBucket('avatars', public: true);
// Upload a file
final bytes = Uint8List.fromList([/* file bytes */]);
final result = await storage.upload('avatars', bytes, fileName: 'photo.png');
// Get a public URL
final url = storage.getPublicUrl('avatars', 'photo.png');
storage.dispose();
Authentication #
import 'package:wowsql/wowsql.dart';
final auth = ProjectAuthClient(
projectUrl: 'your-project-url',
publicApiKey: 'your-public-api-key',
);
// Sign up
final response = await auth.signUp(
email: 'user@example.com',
password: 'securePassword123',
fullName: 'Jane Doe',
);
// Sign in
final session = await auth.signIn(
email: 'user@example.com',
password: 'securePassword123',
);
print(session.accessToken);
auth.dispose();
Features #
- Database CRUD — Full Create, Read, Update, Delete with a fluent query builder
- Advanced Filtering — eq, neq, gt, gte, lt, lte, like, isNull, isNotNull, in, notIn, between
- Pagination & Sorting — limit, offset, orderBy, multi-column ordering
- Aggregation — groupBy, having clauses
- Raw SQL — Execute arbitrary queries with type-safe deserialization
- Schema Management — Create/alter/drop tables, columns, and indexes programmatically
- Authentication — Email/password, OAuth, OTP, magic links, password reset
- Token Management — Pluggable TokenStorage with built-in MemoryTokenStorage
- S3-Compatible Storage — Upload, download, list, delete files with bucket management
- Type Safety — Generic support across queries and responses
- Error Handling — Typed exceptions for auth, network, rate-limit, storage, and more
- Configurable — Custom timeouts, SSL settings, HTTP client injection
Database Operations #
Initializing the Client #
import 'package:wowsql/wowsql.dart';
final client = WOWSQLClient(
projectUrl: 'my-project',
apiKey: 'sk_live_abc123',
baseDomain: 'wowsql.com', // default
secure: true, // default, uses HTTPS
timeout: Duration(seconds: 30), // default
verifySsl: true, // default
);
Listing Tables #
final tables = await client.listTables();
for (final table in tables) {
print(table);
}
// Output: users, products, orders, ...
Getting Table Schema #
final schema = await client.getTableSchema('users');
print('Table: ${schema.tableName}');
for (final col in schema.columns) {
print(' ${col.name} (${col.type}) nullable=${col.nullable} default=${col.defaultValue}');
}
Health Check #
final status = await client.health();
print(status); // {status: ok, database: connected, ...}
Select Queries #
// Select all columns
final all = await client.table('users').select('*').get();
// Select specific columns
final partial = await client.table('users').select('id, name, email').get();
// Access response data
print(partial.data); // List<Map<String, dynamic>>
print(partial.count); // number of rows returned
Filtering #
// Equality
final admins = await client
.table('users')
.select('*')
.eq('role', 'admin')
.get();
// Comparison operators
final expensive = await client
.table('products')
.select('*')
.gte('price', 100)
.lt('price', 500)
.get();
// Using the generic filter method
final active = await client
.table('users')
.select('*')
.filter('status', FilterOperator.eq, 'active')
.get();
// Not equal
final nonAdmins = await client
.table('users')
.select('*')
.neq('role', 'admin')
.get();
Get by ID #
final user = await client.table('users').getById(42);
print(user['name']); // John Doe
Insert (Create) #
// Single insert
final created = await client.table('products').create({
'name': 'Laptop',
'price': 999.99,
'category': 'electronics',
'in_stock': true,
});
print('Inserted ID: ${created.insertedId}');
// insert() is an alias for create()
final created2 = await client.table('products').insert({
'name': 'Keyboard',
'price': 49.99,
});
Bulk Insert #
final result = await client.table('products').bulkInsert([
{'name': 'Mouse', 'price': 25.00, 'category': 'accessories'},
{'name': 'Monitor', 'price': 349.00, 'category': 'electronics'},
{'name': 'Headset', 'price': 79.99, 'category': 'accessories'},
]);
Upsert #
// Insert or update on conflict
final result = await client.table('products').upsert(
{'id': 1, 'name': 'Laptop Pro', 'price': 1299.99},
onConflict: 'id',
);
Update #
final updated = await client.table('products').update(1, {
'price': 899.99,
'name': 'Laptop (Sale)',
});
Delete #
final deleted = await client.table('products').delete(42);
Count #
final total = await client.table('users').count();
print('Total users: $total');
Pagination (Table-level) #
final page = await client.table('users').paginate(1, 20);
print(page.data); // rows for page 1
print(page.count); // total count
Advanced Query Builder #
The QueryBuilder provides a fluent API for building complex queries.
Chained Queries #
final results = await client
.table('orders')
.select('id, customer_id, total, status')
.eq('status', 'completed')
.gte('total', 50.0)
.orderBy('total', SortDirection.desc)
.limit(10)
.get();
Null Checks #
// Find rows where a column is NULL
final noEmail = await client
.table('users')
.select('*')
.isNull('email')
.get();
// Find rows where a column is NOT NULL
final hasEmail = await client
.table('users')
.select('*')
.isNotNull('email')
.get();
LIKE Patterns #
final matches = await client
.table('products')
.select('*')
.like('name', '%phone%')
.get();
IN and NOT IN #
// inList / in_
final selected = await client
.table('orders')
.select('*')
.inList('status', ['pending', 'processing', 'shipped'])
.get();
// notIn
final excluded = await client
.table('orders')
.select('*')
.notIn('status', ['cancelled', 'refunded'])
.get();
BETWEEN #
final midRange = await client
.table('products')
.select('*')
.between('price', 10, 100)
.get();
final outsideRange = await client
.table('products')
.select('*')
.notBetween('price', 10, 100)
.get();
OR Conditions #
final results = await client
.table('products')
.select('*')
.eq('category', 'electronics')
.or('category', FilterOperator.eq, 'accessories')
.get();
GROUP BY and HAVING #
final categorySales = await client
.table('orders')
.select('category, SUM(total) as revenue, COUNT(*) as order_count')
.groupBy(['category'])
.having('SUM(total)', FilterOperator.gte, 1000)
.get();
Ordering #
// Single column
final sorted = await client
.table('products')
.select('*')
.orderBy('price', SortDirection.asc)
.get();
// Multiple columns
final multiSorted = await client
.table('products')
.select('*')
.orderByMultiple([
OrderByItem(column: 'category', direction: SortDirection.asc),
OrderByItem(column: 'price', direction: SortDirection.desc),
])
.get();
// order() is an alias for orderBy()
final sorted2 = await client
.table('products')
.select('*')
.order('created_at', SortDirection.desc)
.get();
Limit and Offset #
final page2 = await client
.table('products')
.select('*')
.orderBy('id')
.limit(20)
.offset(20)
.get();
Pagination (QueryBuilder) #
final page = await client
.table('orders')
.select('*')
.eq('status', 'completed')
.orderBy('created_at', SortDirection.desc)
.paginate(2, 25);
print(page.data); // 25 rows from page 2
print(page.count); // total matching rows
Terminal Methods #
// get() - returns QueryResponse with all matching rows
final response = await client
.table('users')
.select('*')
.eq('active', true)
.get();
// execute() - same as get(), alternative name
final response2 = await client
.table('users')
.select('*')
.execute();
// first() - returns the first matching row or null
final firstUser = await client
.table('users')
.select('*')
.eq('role', 'admin')
.first();
// single() - returns exactly one row, throws if not found
final user = await client
.table('users')
.select('*')
.eq('id', 1)
.single();
// count() - returns total matching rows
final total = await client
.table('users')
.select('*')
.eq('active', true)
.count();
Mutations via QueryBuilder #
// Insert through query builder
final created = await client
.table('products')
.insert({'name': 'Tablet', 'price': 399.99});
// Bulk insert through query builder
final bulk = await client
.table('products')
.bulkInsert([
{'name': 'Case', 'price': 19.99},
{'name': 'Charger', 'price': 29.99},
]);
// Upsert through query builder
final upserted = await client
.table('products')
.upsert(
{'id': 5, 'name': 'Updated Tablet', 'price': 449.99},
onConflict: 'id',
);
// Update by ID
final updated = await client
.table('products')
.select('*')
.updateById(5, {'price': 379.99});
// Delete by ID
final deleted = await client
.table('products')
.select('*')
.deleteById(5);
Type-Safe Queries with Generics #
class Product {
final int id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
factory Product.fromJson(Map<String, dynamic> json) => Product(
id: json['id'],
name: json['name'],
price: (json['price'] as num).toDouble(),
);
}
// Typed get()
final response = await client
.table('products')
.select('*')
.gte('price', 10)
.get<Product>(fromJson: Product.fromJson);
for (final product in response.data) {
print('${product.name}: \$${product.price}');
}
// Typed first()
final cheapest = await client
.table('products')
.select('*')
.orderBy('price', SortDirection.asc)
.first<Product>(fromJson: Product.fromJson);
// Typed paginate()
final page = await client
.table('products')
.select('*')
.paginate<Product>(1, 10, fromJson: Product.fromJson);
Raw SQL Queries #
// Untyped
final rows = await client.query('SELECT * FROM users WHERE active = 1');
// Typed
final users = await client.query<User>(
'SELECT id, name, email FROM users WHERE role = "admin"',
fromJson: User.fromJson,
);
Filter Operators #
The FilterOperator enum provides all supported comparison operators:
| Operator | Enum Value | SQL Equivalent | Shortcut Method |
|---|---|---|---|
| Equal | FilterOperator.eq |
= |
.eq(col, val) |
| Not Equal | FilterOperator.neq |
!= |
.neq(col, val) |
| Greater Than | FilterOperator.gt |
> |
.gt(col, val) |
| Greater or Equal | FilterOperator.gte |
>= |
.gte(col, val) |
| Less Than | FilterOperator.lt |
< |
.lt(col, val) |
| Less or Equal | FilterOperator.lte |
<= |
.lte(col, val) |
| Like | FilterOperator.like |
LIKE |
.like(col, pattern) |
| Is Null | FilterOperator.isNull |
IS NULL |
.isNull(col) |
| Is Not Null | FilterOperator.isNotNull |
IS NOT NULL |
.isNotNull(col) |
| In | FilterOperator.inList |
IN (...) |
.inList(col, vals) |
| Not In | FilterOperator.notIn |
NOT IN (...) |
.notIn(col, vals) |
| Between | FilterOperator.between |
BETWEEN |
.between(col, low, high) |
| Not Between | FilterOperator.notBetween |
NOT BETWEEN |
.notBetween(col, low, high) |
Authentication #
Initializing the Auth Client #
import 'package:wowsql/wowsql.dart';
final auth = ProjectAuthClient(
projectUrl: 'your-project-url',
publicApiKey: 'pk_live_abc123',
baseDomain: 'wowsql.com',
secure: true,
timeout: Duration(seconds: 30),
);
Custom Token Storage #
// The SDK provides MemoryTokenStorage by default.
// Implement TokenStorage for persistent storage:
class SecureTokenStorage implements TokenStorage {
final FlutterSecureStorage _storage = FlutterSecureStorage();
@override
Future<String?> getAccessToken() async {
return await _storage.read(key: 'access_token');
}
@override
Future<String?> getRefreshToken() async {
return await _storage.read(key: 'refresh_token');
}
@override
Future<void> setTokens({required String accessToken, String? refreshToken}) async {
await _storage.write(key: 'access_token', value: accessToken);
if (refreshToken != null) {
await _storage.write(key: 'refresh_token', value: refreshToken);
}
}
@override
Future<void> clear() async {
await _storage.delete(key: 'access_token');
await _storage.delete(key: 'refresh_token');
}
}
// Use it
final auth = ProjectAuthClient(
projectUrl: 'your-project-url',
publicApiKey: 'pk_live_abc123',
tokenStorage: SecureTokenStorage(),
);
Sign Up #
final response = await auth.signUp(
email: 'newuser@example.com',
password: 'StrongP@ssw0rd!',
fullName: 'Jane Doe',
userMetadata: {'referral_code': 'ABC123'},
);
if (response.session != null) {
print('Signed up and logged in');
print('Access token: ${response.session!.accessToken}');
} else {
print('Check email for verification');
}
Sign In #
final response = await auth.signIn(
email: 'user@example.com',
password: 'StrongP@ssw0rd!',
);
print('User: ${response.user?.email}');
print('Token: ${response.session?.accessToken}');
Get Current User #
// Uses stored access token by default
final user = await auth.getUser();
print('Email: ${user.email}');
print('Name: ${user.fullName}');
// Or pass a specific token
final user2 = await auth.getUser(accessToken: 'specific-token');
OAuth #
// Step 1: Get the authorization URL
final oauthResponse = await auth.getOAuthAuthorizationUrl(
provider: 'google',
redirectUri: 'https://myapp.com/callback',
);
print('Redirect to: ${oauthResponse.authorizationUrl}');
// Step 2: After the user is redirected back, exchange the code
final authResponse = await auth.exchangeOAuthCallback(
provider: 'google',
code: 'auth-code-from-callback',
redirectUri: 'https://myapp.com/callback',
);
print('User: ${authResponse.user?.email}');
print('Token: ${authResponse.session?.accessToken}');
Password Management #
// Forgot password (sends reset email)
await auth.forgotPassword(email: 'user@example.com');
// Reset password with token from email
await auth.resetPassword(
token: 'reset-token-from-email',
newPassword: 'NewSecureP@ss!',
);
// Change password (while logged in)
await auth.changePassword(
currentPassword: 'OldPassword123',
newPassword: 'NewPassword456!',
);
OTP (One-Time Password) #
// Send OTP
await auth.sendOtp(
email: 'user@example.com',
purpose: 'login',
);
// Verify OTP
final response = await auth.verifyOtp(
email: 'user@example.com',
otp: '123456',
purpose: 'login',
);
// Verify OTP with password reset
await auth.verifyOtp(
email: 'user@example.com',
otp: '654321',
purpose: 'password_reset',
newPassword: 'BrandNewPassword!',
);
Magic Links #
await auth.sendMagicLink(
email: 'user@example.com',
purpose: 'login',
);
// User clicks link in email -> handled by your app's deep link handler
Email Verification #
// Verify email with token
await auth.verifyEmail(token: 'verification-token');
// Resend verification email
await auth.resendVerification(email: 'user@example.com');
Session Management #
// Get the current session from token storage
final session = await auth.getSession();
if (session != null) {
print('Logged in: ${session.accessToken}');
}
// Manually set a session
await auth.setSession(
accessToken: 'my-access-token',
refreshToken: 'my-refresh-token',
);
// Refresh the access token
final newSession = await auth.refreshToken();
print('New token: ${newSession.session?.accessToken}');
// Refresh with an explicit refresh token
final newSession2 = await auth.refreshToken(refreshToken: 'explicit-refresh-token');
// Logout
await auth.logout();
// Clear local session without server call
await auth.clearSession();
Update User Profile #
final updatedUser = await auth.updateUser(
fullName: 'Jane Smith',
avatarUrl: 'https://example.com/avatar.jpg',
username: 'janesmith',
userMetadata: {'theme': 'dark', 'language': 'en'},
);
print('Updated: ${updatedUser.user?.fullName}');
Dispose #
auth.dispose();
Storage Operations #
Initializing the Storage Client #
import 'package:wowsql/wowsql.dart';
final storage = WOWSQLStorage(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
baseDomain: 'wowsql.com',
secure: true,
timeout: Duration(seconds: 30),
);
Create a Bucket #
final bucket = await storage.createBucket(
'profile-images',
public: true,
fileSizeLimit: 5 * 1024 * 1024, // 5 MB
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
);
print('Created bucket: ${bucket.name}');
List Buckets #
final buckets = await storage.listBuckets();
for (final bucket in buckets) {
print('${bucket.name} (public: ${bucket.public})');
}
Get Bucket Details #
final bucket = await storage.getBucket('profile-images');
print('Name: ${bucket.name}');
print('Public: ${bucket.public}');
print('File size limit: ${bucket.fileSizeLimit}');
print('Allowed types: ${bucket.allowedMimeTypes}');
Update a Bucket #
await storage.updateBucket(
'profile-images',
public: false,
fileSizeLimit: 10 * 1024 * 1024, // 10 MB
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'],
);
Delete a Bucket #
await storage.deleteBucket('old-bucket');
Upload a File #
import 'dart:typed_data';
// Upload raw bytes
final bytes = Uint8List.fromList([/* file bytes */]);
final result = await storage.upload(
'profile-images',
bytes,
path: 'users/42/',
fileName: 'avatar.png',
contentType: 'image/png',
);
print('Uploaded: ${result.path}');
print('Size: ${result.size}');
Upload from File Path #
final result = await storage.uploadFromPath(
'/path/to/local/photo.jpg',
bucket: 'profile-images',
path: 'users/42/',
contentType: 'image/jpeg',
);
List Files #
final files = await storage.listFiles(
'profile-images',
prefix: 'users/42/',
limit: 50,
offset: 0,
);
for (final file in files) {
print('${file.name} - ${file.size} bytes');
}
Download a File #
// Get file bytes
final bytes = await storage.download('profile-images', 'users/42/avatar.png');
// Download to a local file
await storage.downloadToFile(
'profile-images',
'users/42/avatar.png',
'/path/to/local/avatar.png',
);
Delete a File #
await storage.deleteFile('profile-images', 'users/42/avatar.png');
Get a Public URL #
final url = storage.getPublicUrl('profile-images', 'users/42/avatar.png');
print(url); // https://your-project-url.wowsql.com/storage/v1/...
Storage Stats and Quota #
final stats = await storage.getStats();
print('Total files: ${stats['total_files']}');
print('Total size: ${stats['total_size']}');
final quota = await storage.getQuota();
print('Used: ${quota.used}');
print('Limit: ${quota.limit}');
print('Remaining: ${quota.remaining}');
Dispose #
storage.dispose();
Schema Management #
The WOWSQLSchema client requires a service key (not a regular API key) for elevated permissions.
Initializing the Schema Client #
import 'package:wowsql/wowsql.dart';
final schema = WOWSQLSchema(
projectUrl: 'your-project-url',
serviceKey: 'your-service-key',
baseDomain: 'wowsql.com',
secure: true,
);
Create a Table #
await schema.createTable(
name: 'products',
columns: [
{'name': 'id', 'type': 'INT', 'auto_increment': true},
{'name': 'name', 'type': 'VARCHAR(255)', 'nullable': false},
{'name': 'description', 'type': 'TEXT', 'nullable': true},
{'name': 'price', 'type': 'DECIMAL(10,2)', 'nullable': false, 'default': '0.00'},
{'name': 'category', 'type': 'VARCHAR(100)', 'nullable': true},
{'name': 'in_stock', 'type': 'BOOLEAN', 'nullable': false, 'default': 'true'},
{'name': 'created_at', 'type': 'TIMESTAMP', 'default': 'CURRENT_TIMESTAMP'},
],
primaryKey: 'id',
indexes: [
{'columns': ['category'], 'name': 'idx_category'},
{'columns': ['price'], 'name': 'idx_price'},
],
);
Alter a Table #
await schema.alterTable(
name: 'products',
operation: 'ADD',
columnName: 'sku',
columnType: 'VARCHAR(50)',
nullable: true,
);
Drop a Table #
await schema.dropTable('old_table', cascade: true);
Add a Column #
await schema.addColumn(
table: 'products',
columnName: 'weight',
columnType: 'DECIMAL(8,2)',
nullable: true,
defaultValue: '0.00',
);
Drop a Column #
await schema.dropColumn(
table: 'products',
columnName: 'weight',
);
Rename a Column #
await schema.renameColumn(
table: 'products',
columnName: 'name',
newColumnName: 'product_name',
);
Modify a Column #
await schema.modifyColumn(
table: 'products',
columnName: 'description',
columnType: 'MEDIUMTEXT',
nullable: false,
defaultValue: "''",
);
Create an Index #
await schema.createIndex(
table: 'products',
columns: ['category', 'price'],
unique: false,
name: 'idx_category_price',
using: 'BTREE',
);
Execute Raw SQL #
// executeSql and executeSQL are equivalent
final result = await schema.executeSql(
'ALTER TABLE products ADD FULLTEXT INDEX idx_search (product_name, description)',
);
List Tables and Schema #
final tables = await schema.listTables();
print(tables); // ['products', 'orders', 'users', ...]
final tableSchema = await schema.getTableSchema('products');
for (final col in tableSchema.columns) {
print('${col.name}: ${col.type}');
}
Dispose #
schema.dispose();
API Keys #
WowSQL uses different API key types for different purposes:
| Key Type | Prefix | Use Case | Client |
|---|---|---|---|
| Secret API Key | sk_live_ |
Server-side database access | WOWSQLClient, WOWSQLStorage |
| Public API Key | pk_live_ |
Client-side authentication | ProjectAuthClient |
| Service Key | — | Schema management, admin operations | WOWSQLSchema |
Security: Never expose secret API keys or service keys in client-side code. Use public API keys for authentication in Flutter apps. Secret keys should only be used in server-side Dart applications.
Error Handling #
The SDK throws typed exceptions for different error scenarios:
import 'package:wowsql/wowsql.dart';
try {
final response = await client.table('users').select('*').get();
} on AuthenticationException catch (e) {
// Invalid or expired API key / token
print('Auth error: ${e.message}');
} on NotFoundException catch (e) {
// Table or resource not found
print('Not found: ${e.message}');
} on RateLimitException catch (e) {
// Too many requests
print('Rate limited: ${e.message}');
} on NetworkException catch (e) {
// Connection error, timeout, DNS failure
print('Network error: ${e.message}');
} on StorageException catch (e) {
// Storage-specific error
print('Storage error: ${e.message}');
} on StorageLimitExceededException catch (e) {
// Storage quota exceeded
print('Storage limit: ${e.message}');
} on SchemaPermissionException catch (e) {
// Insufficient permissions for schema operations
print('Schema permission error: ${e.message}');
} on WOWSQLException catch (e) {
// Base exception — catches all SDK errors
print('WowSQL error: ${e.message}');
print('Status code: ${e.statusCode}');
}
Exception Hierarchy #
WOWSQLException (base)
├── AuthenticationException — 401/403 errors, invalid credentials
├── NetworkException — Connection failures, timeouts
├── RateLimitException — 429 Too Many Requests
├── NotFoundException — 404 Not Found
├── StorageException — Storage operation failures
│ └── StorageLimitExceededException — Quota exceeded
└── SchemaPermissionException — Insufficient schema privileges
Retry Pattern #
Future<T> withRetry<T>(Future<T> Function() fn, {int maxRetries = 3}) async {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} on RateLimitException {
if (attempt == maxRetries) rethrow;
await Future.delayed(Duration(seconds: (attempt + 1) * 2));
} on NetworkException {
if (attempt == maxRetries) rethrow;
await Future.delayed(Duration(seconds: (attempt + 1) * 1));
}
}
throw StateError('Unreachable');
}
// Usage
final users = await withRetry(() =>
client.table('users').select('*').get()
);
Configuration #
Custom HTTP Client #
import 'package:http/http.dart' as http;
final httpClient = http.Client();
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
httpClient: httpClient,
);
// Also works with Auth, Storage, and Schema clients
final auth = ProjectAuthClient(
projectUrl: 'your-project-url',
publicApiKey: 'pk_live_abc123',
httpClient: httpClient,
);
Timeout Configuration #
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
timeout: Duration(seconds: 60), // longer timeout for large queries
);
SSL Configuration #
// Disable SSL verification (development only)
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
verifySsl: false,
);
Custom Domain #
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
baseDomain: 'custom-domain.com',
secure: true,
);
Flutter Integration #
Provider #
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wowsql/wowsql.dart';
class WowSQLService {
late final WOWSQLClient db;
late final ProjectAuthClient auth;
late final WOWSQLStorage storage;
WowSQLService() {
db = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
);
auth = ProjectAuthClient(
projectUrl: 'your-project-url',
publicApiKey: 'pk_live_abc123',
);
storage = WOWSQLStorage(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
);
}
void dispose() {
db.close();
auth.dispose();
storage.dispose();
}
}
void main() {
runApp(
Provider<WowSQLService>(
create: (_) => WowSQLService(),
dispose: (_, service) => service.dispose(),
child: const MyApp(),
),
);
}
// Use in widgets
class UserList extends StatelessWidget {
const UserList({super.key});
@override
Widget build(BuildContext context) {
final db = context.read<WowSQLService>().db;
return FutureBuilder(
future: db.table('users').select('*').get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
final users = snapshot.data!.data;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) => ListTile(
title: Text(users[index]['name']),
subtitle: Text(users[index]['email']),
),
);
},
);
}
}
Riverpod #
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:wowsql/wowsql.dart';
final wowsqlClientProvider = Provider<WOWSQLClient>((ref) {
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
);
ref.onDispose(() => client.close());
return client;
});
final authClientProvider = Provider<ProjectAuthClient>((ref) {
final auth = ProjectAuthClient(
projectUrl: 'your-project-url',
publicApiKey: 'pk_live_abc123',
);
ref.onDispose(() => auth.dispose());
return auth;
});
final storageProvider = Provider<WOWSQLStorage>((ref) {
final storage = WOWSQLStorage(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
);
ref.onDispose(() => storage.dispose());
return storage;
});
final usersProvider = FutureProvider<List<Map<String, dynamic>>>((ref) async {
final client = ref.watch(wowsqlClientProvider);
final response = await client.table('users').select('*').get();
return response.data;
});
final productsByCategory = FutureProvider.family<List<Map<String, dynamic>>, String>(
(ref, category) async {
final client = ref.watch(wowsqlClientProvider);
final response = await client
.table('products')
.select('*')
.eq('category', category)
.orderBy('name')
.get();
return response.data;
},
);
// Use in widgets
class ProductListPage extends ConsumerWidget {
const ProductListPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsAsync = ref.watch(usersProvider);
return productsAsync.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, i) => ListTile(
title: Text(users[i]['name']),
),
),
);
}
}
Bloc #
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wowsql/wowsql.dart';
// Events
abstract class UserEvent {}
class LoadUsers extends UserEvent {}
class CreateUser extends UserEvent {
final String name;
final String email;
CreateUser({required this.name, required this.email});
}
// States
abstract class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final List<Map<String, dynamic>> users;
UserLoaded(this.users);
}
class UserError extends UserState {
final String message;
UserError(this.message);
}
// Bloc
class UserBloc extends Bloc<UserEvent, UserState> {
final WOWSQLClient client;
UserBloc(this.client) : super(UserInitial()) {
on<LoadUsers>(_onLoadUsers);
on<CreateUser>(_onCreateUser);
}
Future<void> _onLoadUsers(LoadUsers event, Emitter<UserState> emit) async {
emit(UserLoading());
try {
final response = await client.table('users').select('*').get();
emit(UserLoaded(response.data));
} on WOWSQLException catch (e) {
emit(UserError(e.message));
}
}
Future<void> _onCreateUser(CreateUser event, Emitter<UserState> emit) async {
try {
await client.table('users').create({
'name': event.name,
'email': event.email,
});
add(LoadUsers());
} on WOWSQLException catch (e) {
emit(UserError(e.message));
}
}
}
Examples #
Complete CRUD Application #
import 'package:wowsql/wowsql.dart';
Future<void> main() async {
final client = WOWSQLClient(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
);
// CREATE
final created = await client.table('tasks').create({
'title': 'Learn WowSQL',
'completed': false,
'priority': 'high',
});
final taskId = created.insertedId;
print('Created task #$taskId');
// READ
final tasks = await client
.table('tasks')
.select('*')
.eq('completed', false)
.orderBy('priority')
.get();
print('Pending tasks: ${tasks.count}');
// UPDATE
await client.table('tasks').update(taskId, {
'completed': true,
});
print('Task #$taskId marked complete');
// DELETE
await client.table('tasks').delete(taskId);
print('Task #$taskId deleted');
client.close();
}
File Upload with Error Handling #
import 'dart:io';
import 'package:wowsql/wowsql.dart';
Future<void> uploadUserAvatar(int userId, String filePath) async {
final storage = WOWSQLStorage(
projectUrl: 'your-project-url',
apiKey: 'sk_live_abc123',
);
try {
final result = await storage.uploadFromPath(
filePath,
bucket: 'avatars',
path: 'users/$userId/',
contentType: 'image/jpeg',
);
final url = storage.getPublicUrl('avatars', result.path);
print('Avatar uploaded: $url');
} on StorageLimitExceededException {
print('Storage quota exceeded. Please upgrade your plan.');
} on StorageException catch (e) {
print('Upload failed: ${e.message}');
} finally {
storage.dispose();
}
}
Authentication Flow #
import 'package:wowsql/wowsql.dart';
class AuthService {
final ProjectAuthClient _auth;
AuthService(String projectUrl, String publicKey)
: _auth = ProjectAuthClient(
projectUrl: projectUrl,
publicApiKey: publicKey,
);
Future<AuthUser?> signIn(String email, String password) async {
try {
final response = await _auth.signIn(email: email, password: password);
return response.user;
} on AuthenticationException catch (e) {
print('Login failed: ${e.message}');
return null;
}
}
Future<bool> signUp(String email, String password, String name) async {
try {
await _auth.signUp(
email: email,
password: password,
fullName: name,
);
return true;
} on WOWSQLException catch (e) {
print('Sign up failed: ${e.message}');
return false;
}
}
Future<bool> isLoggedIn() async {
final session = await _auth.getSession();
return session != null;
}
Future<void> signOut() async {
await _auth.logout();
await _auth.clearSession();
}
void dispose() => _auth.dispose();
}
Schema Migration Script #
import 'package:wowsql/wowsql.dart';
Future<void> migrate() async {
final schema = WOWSQLSchema(
projectUrl: 'your-project-url',
serviceKey: 'your-service-key',
);
// Create the orders table
await schema.createTable(
name: 'orders',
columns: [
{'name': 'id', 'type': 'INT', 'auto_increment': true},
{'name': 'user_id', 'type': 'INT', 'nullable': false},
{'name': 'total', 'type': 'DECIMAL(10,2)', 'nullable': false},
{'name': 'status', 'type': "ENUM('pending','processing','shipped','delivered')", 'default': "'pending'"},
{'name': 'created_at', 'type': 'TIMESTAMP', 'default': 'CURRENT_TIMESTAMP'},
],
primaryKey: 'id',
indexes: [
{'columns': ['user_id'], 'name': 'idx_user_id'},
{'columns': ['status'], 'name': 'idx_status'},
],
);
// Add a column to an existing table
await schema.addColumn(
table: 'orders',
columnName: 'shipping_address',
columnType: 'TEXT',
nullable: true,
);
// Create an index
await schema.createIndex(
table: 'orders',
columns: ['created_at', 'status'],
name: 'idx_created_status',
);
print('Migration complete');
schema.dispose();
}
Paginated List View #
import 'package:flutter/material.dart';
import 'package:wowsql/wowsql.dart';
class PaginatedProductList extends StatefulWidget {
final WOWSQLClient client;
const PaginatedProductList({super.key, required this.client});
@override
State<PaginatedProductList> createState() => _PaginatedProductListState();
}
class _PaginatedProductListState extends State<PaginatedProductList> {
final List<Map<String, dynamic>> _products = [];
int _page = 1;
final int _perPage = 20;
bool _loading = false;
bool _hasMore = true;
@override
void initState() {
super.initState();
_loadMore();
}
Future<void> _loadMore() async {
if (_loading || !_hasMore) return;
setState(() => _loading = true);
try {
final response = await widget.client
.table('products')
.select('*')
.orderBy('name')
.paginate(_page, _perPage);
setState(() {
_products.addAll(response.data);
_page++;
_hasMore = response.data.length == _perPage;
_loading = false;
});
} on WOWSQLException catch (e) {
setState(() => _loading = false);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${e.message}')),
);
}
}
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification.metrics.pixels >=
notification.metrics.maxScrollExtent - 200) {
_loadMore();
}
return false;
},
child: ListView.builder(
itemCount: _products.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _products.length) {
return const Center(child: CircularProgressIndicator());
}
final product = _products[index];
return ListTile(
title: Text(product['name']),
subtitle: Text('\$${product['price']}'),
trailing: Text(product['category'] ?? ''),
);
},
),
);
}
}
Models Reference #
Response Models #
| Model | Key Fields |
|---|---|
QueryResponse<T> |
data (List<T>), count (int) |
CreateResponse |
insertedId (dynamic), message (String) |
UpdateResponse |
affectedRows (int), message (String) |
DeleteResponse |
affectedRows (int), message (String) |
Schema Models #
| Model | Key Fields |
|---|---|
TableSchema |
tableName (String), columns (List<ColumnInfo>) |
ColumnInfo |
name, type, nullable, defaultValue, primaryKey, autoIncrement |
Storage Models #
| Model | Key Fields |
|---|---|
StorageBucket |
name, public, fileSizeLimit, allowedMimeTypes |
StorageFile |
name, size, contentType, lastModified, path |
StorageQuota |
used, limit, remaining |
FileUploadResult |
path, size, contentType |
Auth Models #
| Model | Key Fields |
|---|---|
AuthUser |
id, email, fullName, username, avatarUrl, userMetadata |
AuthSession |
accessToken, refreshToken, expiresAt |
AuthResponse |
user (AuthUser?), session (AuthSession?), message |
OAuthAuthorizationResponse |
authorizationUrl, provider |
Query Models #
| Model | Key Fields |
|---|---|
HavingFilter |
column, operator, value |
OrderByItem |
column, direction (SortDirection) |
Enums #
FilterOperator #
enum FilterOperator {
eq, // Equal (=)
neq, // Not equal (!=)
gt, // Greater than (>)
gte, // Greater than or equal (>=)
lt, // Less than (<)
lte, // Less than or equal (<=)
like, // LIKE pattern matching
isNull, // IS NULL
isNotNull, // IS NOT NULL
inList, // IN (values)
notIn, // NOT IN (values)
between, // BETWEEN low AND high
notBetween, // NOT BETWEEN low AND high
}
SortDirection #
enum SortDirection {
asc, // Ascending
desc, // Descending
}
Requirements #
- Dart: 2.17 or later
- Flutter: 3.0 or later (for Flutter projects)
- Dependencies:
httppackage (included automatically)
The SDK works with both pure Dart and Flutter projects.
Resources #
License #
MIT License. See LICENSE for details.