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

pub package License: MIT Dart 2.17+ Flutter 3.0+

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!',
);
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: http package (included automatically)

The SDK works with both pure Dart and Flutter projects.


Resources #

License #

MIT License. See LICENSE for details.

0
likes
160
points
38
downloads

Documentation

Documentation
API reference

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

License

MIT (license)

Dependencies

http

More

Packages that depend on wowsql