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.

Libraries

wowsql
Official Flutter/Dart SDK for WowSQL