gbaas_sdk 2.0.0
gbaas_sdk: ^2.0.0 copied to clipboard
Dart SDK for BaaS (Backend as a Service) - auth, database, storage, realtime, functions, admin APIs.
gbaas_sdk #
Official Dart SDK for BaaS (Backend as a Service). Pure Dart — works in Flutter, server-side Dart, and Dart CLI.
What's new in 2.0.0 #
- Security fix:
validateResetTokenno longer always returnstrue. Upgrade if you ship a password-reset UI. BaasClient.onAuthStateChangebroadcast stream (signedIn/signedOut/sessionExpired).- Pluggable
SessionStorage— drop influtter_secure_storage(or anything) to persist the JWT. - 401 auto-handling: any authenticated request that comes back 401 clears the session, emits
sessionExpired, and throwsUnauthorizedException. - Query builder:
isNull/isNotNulland the renamedinFilter(wasinList) now emit correct PostgREST syntax. - Storage:
uploadactually respectsbucket(sent asbucket_id) andpath;getFilereturnsStorageFileWithUrl. - Realtime: parses concatenated JSON frames so coalesced events are no longer dropped; documents the
role=adminbackend limitation.
See CHANGELOG.md for the full breaking-changes list and KNOWN_ISSUES.md for what remains.
Installation #
dart pub add gbaas_sdk
Migration from 1.x #
// 1.x
await client.from('users').inList('status', ['active', 'pending']).get();
// 2.0
await client.from('users').inFilter('status', ['active', 'pending']).get();
// 1.x — getFile returned a (broken, empty) StorageFile
final file = await client.storage.getFile('id');
print(file.name); // ''
// 2.0 — getFile returns metadata + signed URL
final result = await client.storage.getFile('id');
print(result.file.name); // 'photo.jpg'
print(result.url); // 'https://.../photo.jpg?X-Amz-...'
// 1.x — updateProfile (removed; was a 404)
await client.auth.updateProfile(email: '...');
// 2.0 — use admin users API or your own profile table.
// 1.x — restoreSession required the token
await client.auth.restoreSession(token);
// 2.0 — reads from SessionStorage by default
await client.auth.restoreSession();
Session persistence (Flutter) #
The SDK keeps the token in memory by default. Implement SessionStorage
once with your favorite secure store and pass it to BaasClient:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:gbaas_sdk/gbaas_sdk.dart';
class SecureSessionStorage implements SessionStorage {
final _store = const FlutterSecureStorage();
@override Future<String?> readToken() => _store.read(key: 'baas_token');
@override Future<void> writeToken(String t) => _store.write(key: 'baas_token', value: t);
@override Future<void> deleteToken() => _store.delete(key: 'baas_token');
}
final baas = BaasClient(
baseUrl: '...',
apiKey: '...',
sessionStorage: SecureSessionStorage(),
);
// At boot: pick up the persisted token, validate it against /api/profile.
await baas.auth.restoreSession();
// React to lifecycle events.
baas.onAuthStateChange.listen((s) {
if (s == AuthState.sessionExpired) {
// Redirect to login screen.
}
});
Known limitations #
- Realtime —
role=adminonly: the backend currently emits events only to clients authenticated withrole=admin. Subscribing with aclient/driver/anontoken connects successfully but no events arrive. SeeKNOWN_ISSUES.md. - MFA endpoints: shape mismatches around setup/enable/finalize against the current backend; methods stay on the surface but expect to revisit them. See
KNOWN_ISSUES.md.
Quick Start #
import 'package:gbaas_sdk/gbaas_sdk.dart';
final baas = BaasClient(
baseUrl: 'https://your-baas-instance.com',
apiKey: 'baas_sk_...',
);
// Auth
await baas.auth.login(email: 'user@example.com', password: 'password');
// Database
final users = await baas.from('users').select().get();
// Storage
await baas.storage.upload(bucket: 'avatars', path: 'user.png', bytes: bytes, filename: 'avatar.png');
// Realtime
baas.realtime.subscribe('posts', (event) => print(event));
// Functions
final result = await baas.functions.invoke('my-function', body: {'key': 'value'});
Modules #
Authentication #
// Register
final session = await baas.auth.register(
email: 'new@example.com',
password: 'password123',
);
// Login
final session = await baas.auth.login(
email: 'user@example.com',
password: 'password',
);
// MFA
final mfaSetup = await baas.auth.setupMfa();
await baas.auth.enableMfa(code: '123456');
// OAuth
final providers = await baas.auth.getProviders();
final url = baas.auth.getOAuthUrl('google');
// Password reset
await baas.auth.forgotPassword(email: 'user@example.com');
await baas.auth.resetPassword(token: 'reset-token', newPassword: 'newpass');
// Email verification
await baas.auth.requestEmailVerification();
await baas.auth.verifyEmail(token: 'token');
// Profile
final user = await baas.auth.getProfile();
await baas.auth.updateProfile(email: 'new@email.com');
// Session
await baas.auth.restoreSession('saved-token');
await baas.auth.logout();
Database #
// Query with filters
final posts = await baas.from('posts')
.select()
.eq('status', 'published')
.gte('created_at', '2024-01-01')
.orderDesc('created_at')
.limit(10)
.get();
// Get single record
final post = await baas.from('posts').getById('post-id');
// Insert
final newPost = await baas.from('posts').insert({
'title': 'New Post',
'content': 'Content here',
});
// Update
await baas.from('posts').update('post-id', {
'title': 'Updated Title',
});
// Delete
await baas.from('posts').delete('post-id');
// Raw SQL (admin only)
final results = await baas.database.executeRawSql('SELECT * FROM users LIMIT 10');
Filter Operators
.eq('column', value) // =
.neq('column', value) // !=
.gt('column', value) // >
.gte('column', value) // >=
.lt('column', value) // <
.lte('column', value) // <=
.like('column', '%pattern%')
.ilike('column', '%pattern%') // case insensitive
.isNull('column')
.isNotNull('column')
.inList('column', [1, 2, 3])
Storage #
// Create bucket
final bucket = await baas.storage.createBucket(name: 'avatars', isPublic: true);
// Upload file
final result = await baas.storage.upload(
bucket: 'avatars',
path: 'user123/avatar.png',
bytes: fileBytes,
filename: 'avatar.png',
);
// List files
final files = await baas.storage.listFiles(bucketId: 'avatars');
// Get download URL
final url = await baas.storage.getPresignedUrl(fileId: 'file-id', expiresIn: 3600);
// Delete file
await baas.storage.deleteFile('file-id');
Realtime #
// Subscribe to all changes
final sub = baas.realtime.subscribe('posts', (event) {
print('Event: ${event.type} on ${event.table}');
print('Data: ${event.newData}');
});
// Subscribe to specific events
baas.realtime.onInsert('posts', (event) => print('New post!'));
baas.realtime.onUpdate('posts', (event) => print('Post updated!'));
baas.realtime.onDelete('posts', (event) => print('Post deleted!'));
// Connection state
baas.realtime.onConnectionStateChange = (state) {
print('Connection: $state');
};
// Unsubscribe
sub.unsubscribe();
// Disconnect
baas.realtime.disconnect();
Edge Functions #
// Execute by ID
final result = await baas.functions.invoke('function-id', body: {'key': 'value'});
print(result.isSuccess ? result.data : result.error);
// Execute by name
final result = await baas.functions.invokeByName('my-function', body: {'key': 'value'});
// Execute code directly
final result = await baas.functions.executeCode(code: 'return { hello: "world" };');
// CRUD
final fns = await baas.functions.list();
await baas.functions.create(name: 'my-fn', code: 'return {};', language: 'javascript');
await baas.functions.update('id', name: 'new-name');
await baas.functions.delete('id');
// Hooks (attach functions to events)
await baas.functions.createHook(eventType: 'INSERT', functionId: 'fn-id', tableName: 'posts');
final hooks = await baas.functions.listHooks();
await baas.functions.deleteHook('hook-id');
GraphQL #
final result = await baas.graphql.query(
query: '{ users { id email } }',
variables: {'limit': 10},
);
final schema = await baas.graphql.getSchema();
final playgroundUrl = baas.graphql.getPlaygroundUrl();
Admin Modules #
Webhooks #
final webhooks = await baas.webhooks.list();
final wh = await baas.webhooks.create(url: 'https://example.com/hook', events: ['INSERT']);
await baas.webhooks.update('id', url: 'https://new-url.com');
await baas.webhooks.test('id');
await baas.webhooks.delete('id');
Policies (RLS) #
final policies = await baas.policies.list();
await baas.policies.create(name: 'read_own', table: 'posts', operation: 'SELECT', expression: 'user_id = auth.uid()');
await baas.policies.delete('id');
API Keys #
final keys = await baas.apiKeys.list();
await baas.apiKeys.create(name: 'mobile-app');
await baas.apiKeys.revoke('id');
await baas.apiKeys.delete('id');
// Instance token
final token = await baas.apiKeys.getInstanceToken();
await baas.apiKeys.regenerateInstanceToken();
Backups #
final backups = await baas.backups.list();
await baas.backups.create(description: 'Before migration');
final backup = await baas.backups.get('id');
await baas.backups.restore('id');
final url = await baas.backups.getDownloadUrl('id');
await baas.backups.delete('id');
// Import
final tables = await baas.backups.listTables();
await baas.backups.importFile(table: 'users', bytes: csvBytes, filename: 'users.csv', format: 'csv');
Migrations #
final migrations = await baas.migrations.list();
await baas.migrations.create(name: 'add_posts_table', sql: 'CREATE TABLE posts (...)');
await baas.migrations.apply('id');
await baas.migrations.rollback('id');
await baas.migrations.delete('id');
// Auto-generate migration from current schema diff
final diff = await baas.migrations.generate();
Email #
// Send email
await baas.email.send(to: 'user@example.com', subject: 'Welcome', body: '<h1>Hello!</h1>');
// Email config
final config = await baas.email.getConfig();
await baas.email.saveConfig(config: {'provider': 'smtp', 'host': 'smtp.gmail.com'});
// Templates
final templates = await baas.email.listTemplates();
await baas.email.createTemplate(name: 'welcome', subject: 'Welcome!', body: '<h1>Hi {{name}}</h1>');
await baas.email.updateTemplate('id', subject: 'Updated subject');
await baas.email.deleteTemplate('id');
// Logs
final logs = await baas.email.getLogs();
Cron Jobs #
final jobs = await baas.jobs.list();
await baas.jobs.create(name: 'cleanup', schedule: '0 0 * * *', functionId: 'fn-id');
await baas.jobs.update('id', schedule: '0 */6 * * *');
await baas.jobs.toggle('id');
await baas.jobs.runNow('id');
final execs = await baas.jobs.getExecutions('id');
await baas.jobs.delete('id');
Environment Variables #
final vars = await baas.envVars.list();
await baas.envVars.create(key: 'STRIPE_KEY', value: 'sk_live_...');
await baas.envVars.update('id', value: 'new-value');
await baas.envVars.delete('id');
Database Branches #
final branches = await baas.branches.list();
await baas.branches.create(name: 'feature-x', sourceBranch: 'main');
await baas.branches.merge('branch-id');
await baas.branches.reset('branch-id');
await baas.branches.delete('branch-id');
Log Drains #
final drains = await baas.logDrains.list();
await baas.logDrains.create(name: 'datadog', type: 'datadog', config: {'api_key': '...'});
await baas.logDrains.update('id', config: {'api_key': 'new-key'});
await baas.logDrains.toggle('id');
await baas.logDrains.test('id');
await baas.logDrains.delete('id');
Audit Logs #
final logs = await baas.audit.list(action: 'LOGIN', limit: 50);
final actions = await baas.audit.getActions();
Metrics #
final stats = await baas.metrics.getDashboardStats();
final requestLogs = await baas.metrics.getRequestLogs(limit: 100);
final appLogs = await baas.metrics.getApplicationLogs(level: 'error');
final timeseries = await baas.metrics.getTimeseries(metric: 'requests', period: '24h');
IP Whitelist #
final ips = await baas.ipWhitelist.list();
await baas.ipWhitelist.create(ip: '192.168.1.0/24', description: 'Office');
await baas.ipWhitelist.delete('id');
CORS Origins #
final origins = await baas.corsOrigins.list();
await baas.corsOrigins.create(origin: 'https://myapp.com');
await baas.corsOrigins.delete('id');
Environments #
final status = await baas.environments.status();
await baas.environments.init(name: 'staging');
await baas.environments.promote(from: 'staging', to: 'production');
await baas.environments.revert(environment: 'production');
Search #
final results = await baas.search.search(query: 'hello', table: 'posts');
await baas.search.createIndex(table: 'posts', columns: ['title', 'content']);
Users (Admin) #
final users = await baas.users.list(page: 1, limit: 50);
await baas.users.update('user-id', role: 'admin');
await baas.users.delete('user-id');
Flutter Integration #
import 'package:flutter/material.dart';
import 'package:gbaas_sdk/gbaas_sdk.dart';
final baas = BaasClient(
baseUrl: 'http://10.0.2.2:8086', // Android emulator localhost
apiKey: 'baas_sk_...',
);
Persist Session #
import 'package:shared_preferences/shared_preferences.dart';
// Save token after login
final session = await baas.auth.login(email: email, password: password);
final prefs = await SharedPreferences.getInstance();
await prefs.setString('access_token', session.accessToken);
// Restore on app start
final token = prefs.getString('access_token');
if (token != null) {
await baas.auth.restoreSession(token);
}
Error Handling #
try {
await baas.auth.login(email: email, password: password);
} on AuthException catch (e) {
if (e.data?['mfa_required'] == true) {
// Handle MFA
final tempToken = e.data!['temp_token'];
await baas.auth.verifyMfa(tempToken: tempToken, code: userCode);
} else {
print('Auth error: ${e.message}');
}
} on ValidationException catch (e) {
print('Validation error: ${e.errors}');
} on BaasException catch (e) {
print('Error: ${e.message} (${e.statusCode})');
}
Cleanup #
baas.dispose(); // Closes WebSocket and HTTP connections
License #
MIT