aquabase 0.5.5
aquabase: ^0.5.5 copied to clipboard
Flutter SDK for Aquabase — encrypted real-time database with offline sync, tags, and binary transport.
Aquabase Flutter SDK #
Ultra-fast offline-first database for Flutter. Automatic cloud sync, zero code generation.
Install #
dependencies:
aquabase:
path: ../aquabase_flutter # or git/pub reference
Quick Start #
import 'package:aquabase/aquabase.dart';
final app = await Aquabase.init(
projectId: 'your_project_id',
apiKey: 'aq_pub_...',
);
// Database (instant local cache + background sync)
await app.collection('users').doc('u1').set({'name': 'Ana', 'age': 25});
final user = await app.collection('users').doc('u1').get();
// Auth
final result = await app.auth.login('user@example.com', 'password123');
// Storage
await app.storage.upload('avatars', 'photo.jpg', bytes);
// Logs
await app.logs.connect();
app.logs.info('db', 'User created account');
How It Works #
- All reads/writes go to the local cache — instant, no network latency
- When online, changes sync to the server in background
- When offline, failed ops queue to disk automatically
- On reconnect, the queue flushes to the server — no data loss
Collections #
final users = app.collection('users');
await users.doc('u1').set({'name': 'Ana'});
final user = await users.doc('u1').get();
await users.doc('u1').delete();
Bulk Operations #
await users.bulkSet({
'u1': {'name': 'Ana'},
'u2': {'name': 'Luis'},
'u3': {'name': 'Carlos'},
});
final results = await users.bulkGet(['u1', 'u2', 'u3']);
await users.bulkDelete(['u1', 'u2', 'u3']);
Real-Time #
Watch a document or a query for changes — emits current data immediately, then on every update from any device.
// Watch a single document
final sub = users.watch('u1').listen((user) {
if (user != null) renderUser(user);
});
// Watch a tag query
final sub2 = users.watch({'status': 'active'}).listen((results) {
results.forEach((id, data) => print('$id: $data'));
});
// Stop watching
sub.cancel();
sub2.cancel();
Secondary Index (Tags) #
Tag documents with field-value pairs and query them with AND logic. Tags are kept in a RAM index on the server — queries are instant.
final users = app.collection('users');
// Auto-generated ID (like Firebase .add())
final docId = await users.setWithTags(null, {'name': 'Ana'}, {
'status': 'active',
'role': 'admin',
});
// docId = '1710367200123456_a7f3'
// Custom ID (optional)
await users.setWithTags('u1', {'name': 'Ana'}, {
'status': 'active',
'role': 'admin',
});
// Query: returns only IDs (fast — no disk I/O)
final ids = await users.query({'status': 'active', 'role': 'admin'});
// Hydrate only what you need
final docs = await users.bulkGet(ids);
// Or get IDs + data in one call
final results = await users.queryWithData({'status': 'active'});
Cleanup is automatic: doc.delete() and doc.set() (without tags) remove the document from all indexes.
Local Collections #
Keep data only on the device — no server sync, no offline queue. Useful for caching data from external sources (Firebase, REST APIs, etc).
// Normal collection — syncs to server
final attendances = app.collection('attendances');
// Local collection — data stays on the device only
final cache = app.collection('students_cache', local: true);
// Same API, zero server interaction
await cache.doc('s1').set({'name': 'Ana', 'qr': 'ABC123'});
final student = await cache.doc('s1').get();
await cache.doc('s1').delete();
// Bulk operations also stay local
await cache.bulkSet({
's1': {'name': 'Ana'},
's2': {'name': 'Luis'},
});
Local collections use the same L1 (SIEVE memory) + L2 (disk) cache as normal collections. They persist across app restarts but never touch the network.
Note: A collection name cannot be both local and remote simultaneously. Attempting to create
collection('users')andcollection('users', local: true)will trigger an assertion error.
Date Handling #
DateTime objects are automatically serialized as milliseconds since epoch (UTC). On read, timestamps are returned as int — convert in your app:
// Write
await users.doc('u1').set({
'name': 'Ana',
'createdAt': DateTime.now(),
});
// Read
final user = await users.doc('u1').get();
final date = DateTime.fromMillisecondsSinceEpoch(user!['createdAt'], isUtc: true);
This format is cross-platform compatible with the Node/Web SDK.
Offline-First #
Works without connection. Writes go to local cache and queue for sync.
await app.collection('users').doc('u1').set({'name': 'Ana'});
// Check pending ops
print(app.pendingOfflineOps);
// On reconnect, queue flushes automatically
// Or flush manually:
await app.flushOfflineQueue();
Encryption #
final app = await Aquabase.init(
projectId: 'your_project_id',
apiKey: 'aq_pub_...',
enableCrypto: true, // AES-256-GCM
);
Custom Server URL #
final app = await Aquabase.init(
projectId: 'your_project_id',
apiKey: 'aq_pub_...',
url: 'http://localhost:3280',
);
Auth #
// Email/password
final result = await app.auth.register('user@example.com', 'password123');
final login = await app.auth.login('user@example.com', 'password123');
// OAuth (Google, GitHub)
final google = await app.auth.signInWithOAuth(OAuthProvider.google, googleIdToken);
final github = await app.auth.signInWithOAuth(OAuthProvider.github, githubCode);
// Reconnect with user identity
final userApp = await Aquabase.init(
projectId: 'your_project_id',
apiKey: 'aq_pub_...',
jwt: result.token,
);
Storage #
await app.storage.createBucket(name: 'avatars', public: true);
final bytes = await File('photo.jpg').readAsBytes();
await app.storage.upload('avatars', 'user1.jpg', bytes, contentType: 'image/jpeg');
final files = await app.storage.listFiles('avatars');
final data = await app.storage.download('avatars', 'user1.jpg');
await app.storage.deleteFile('avatars', 'user1.jpg');
Logs #
await app.logs.connect();
app.logs.info('db', 'User created account');
app.logs.warn('auth', 'Invalid password attempt', source: '192.168.1.1');
app.logs.error('ws', 'Connection timeout');
final records = await app.logs.query(LogQueryOptions(
since: DateTime.now().subtract(Duration(hours: 1)).microsecondsSinceEpoch * 1000,
minLevel: LogLevel.warn,
channel: 'auth',
limit: 100,
));
Crash Capture #
Enabled by default when connect() is called. Unhandled exceptions and Flutter errors are sent to the server automatically.
await app.logs.connect(); // Crashes captured automatically
API Reference #
Aquabase #
| Method | Description |
|---|---|
Aquabase.init(...) |
Initialize with named parameters |
Aquabase.instance |
Global access after init |
collection(name) |
Get a collection (synced) |
collection(name, local: true) |
Get a local-only collection |
isConnected |
WebSocket connection status |
pendingOfflineOps |
Pending offline operations count |
flushOfflineQueue() |
Manually sync pending ops |
connectionState |
Stream of connection changes |
deniedOperations |
Stream of blockIds rejected by security rules |
flushResults |
Stream of FlushResult after each sync |
dispose() |
Release resources |
Init Options #
| Parameter | Type | Default | Description |
|---|---|---|---|
projectId |
String |
— | Project ID (required) |
apiKey |
String |
— | API key (required) |
url |
String |
Aquabase cloud | Server URL |
jwt |
String |
null |
JWT for authenticated users |
enableCrypto |
bool |
false |
Enable AES-256-GCM encryption |
enableCache |
bool |
true |
Enable L1/L2 local cache |
Collection #
| Method | Description |
|---|---|
doc(id) |
Document reference |
bulkSet(docs) |
Batch write |
bulkGet(ids) |
Batch read |
bulkDelete(ids) |
Batch delete |
setWithTags(id?, data, tags) |
Write with tags. ID auto-generated if null. Returns docId |
query(tags) |
Query index → IDs only (fast) |
queryWithData(tags) |
Query index → IDs + data |
generateId() |
Generate a unique document ID |
watch('id') |
Real-time doc stream. Returns Stream |
watch({tags}) |
Real-time query stream. Returns Stream |
isLocal |
Whether the collection is local-only |
Document #
| Method | Description |
|---|---|
set(data) |
Write to cache + sync (or cache-only if local) |
get() |
Read from cache, fallback server (cache-only if local) |
delete() |
Delete from cache + sync (or cache-only if local) |
Auth #
| Method | Description |
|---|---|
app.auth.register(email, password) |
Register user, returns JWT + uid |
app.auth.login(email, password) |
Login user, returns JWT + uid |
app.auth.signInWithOAuth(provider, credential) |
OAuth login (Google/GitHub), auto-registers |
app.auth.refresh(token) |
Refresh an expired JWT |
Storage #
| Method | Description |
|---|---|
app.storage.listBuckets() |
List all buckets |
app.storage.createBucket(name, compression?, pub?) |
Create a new bucket |
app.storage.deleteBucket(name) |
Delete a bucket and all its files |
app.storage.listFiles(bucket) |
List files in a bucket |
app.storage.upload(bucket, path, bytes, type?) |
Upload a file (overwrites) |
app.storage.download(bucket, path) |
Download file as bytes |
app.storage.deleteFile(bucket, path) |
Delete a file |
Logs #
| Method | Description |
|---|---|
app.logs.connect() |
Connect + enable crash capture |
app.logs.info(channel, msg) |
Push info-level log |
app.logs.warn(channel, msg) |
Push warn-level log |
app.logs.error(channel,msg) |
Push error-level log |
app.logs.flush() |
Flush buffer to server |
app.logs.query(opts?) |
Query stored logs |
app.logs.dispose() |
Flush + disconnect |
Architecture #
| Component | Implementation |
|---|---|
| Serialization | MsgPack — DateTime → ms epoch auto-conversion |
| L1 Cache | SIEVE eviction (NSDI '24) — lazy promotion |
| L2 Storage | CompactCache — read-only snapshot + append-only disk |
| Cloud Sync | WebSocket with auto-reconnect + backoff |
| Offline Queue | Persisted to disk, auto-flush on reconnect |
| Encryption | AES-256-GCM |
License #
Proprietary