Aquabase Flutter SDK
Encrypted real-time database for Flutter. Offline-first with automatic cloud sync, binary transport, and zero code generation.
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 query
final sub2 = users.where('status', eq: 'active').watch().listen((results) {
results.forEach((id, data) => print('$id: $data'));
});
// Stop watching
sub.cancel();
sub2.cancel();
Indexes
Los índices son automáticos. Al usar .where() por primera vez en un campo, el SDK lo registra como indexado y lo sincroniza con el servidor. De ahí en adelante, cada set() genera los tags correctos automáticamente.
Además, al inicializar, el SDK descarga del servidor la lista de campos ya indexados por otras apps del mismo proyecto. Esto garantiza que si QRline registra studentId como índice, Educanet escribirá con ese tag desde el primer set(), sin necesidad de coordinar manualmente.
Igualdad
final attendances = app.collection('attendances');
// Un día exacto
final results = await attendances
.where('date', eq: '2026-03-18')
.where('type', eq: 'entrada')
.get();
// Watch en tiempo real
attendances
.where('date', eq: '2026-03-18')
.watch()
.listen((results) => print(results.length));
Rangos
Usa from/to para rangos inclusivos (el caso más común). El SDK selecciona automáticamente el índice BTreeMap.
final scores = app.collection('scores');
// Scores entre 80 y 100
final high = await scores
.where('score', from: 80, to: 100)
.get();
// Asistencias del mes de marzo
final march = await attendances
.where('date', from: '2026-03-01', to: '2026-03-31')
.where('studentId', eq: 'stu_001') // filtro adicional de igualdad
.get();
// Un solo día (usa eq, no range)
final today = await attendances
.where('date', eq: '2026-03-20')
.get();
Todos los operadores disponibles:
| Parámetro | Tipo | Descripción |
|---|---|---|
eq |
cualquier | Igualdad exacta |
from |
num / String | Inicio de rango (≥ inclusivo) |
to |
num / String | Fin de rango (≤ inclusivo) |
gt |
num / String | Mayor que (> exclusivo) |
lt |
num / String | Menor que (< exclusivo) |
Datos históricos: Los documentos escritos antes de registrar un campo como
rangeno se re-indexan automáticamente. Llama aset()de nuevo sobre esos docs para que aparezcan en range queries.
Local Collections
Same API as any collection, but data stays on the device — no server sync. Useful when you need a local database that works alongside your cloud collections.
final students = app.collection('_students', local: true);
// Same API as any collection
await students.doc('s1').set({'name': 'Ana', 'age': 12, 'grade': '6A'});
final student = await students.doc('s1').get();
await students.doc('s1').delete();
await students.bulkSet({
's1': {'name': 'Ana', 'age': 12},
's2': {'name': 'Luis', 'age': 13},
});
A collection name cannot be both local and remote simultaneously.
Encryption
AES-256-GCM encryption, two modes:
// Per-collection: only sensitive collections are encrypted
final session = app.collection('_session', local: true, encrypted: true);
await session.doc('current').set({'jwt': token, 'email': 'user@test.com'});
// Global: ALL collections are encrypted
final app = await Aquabase.init(
projectId: 'your_project_id',
apiKey: 'aq_pub_...',
encrypted: true,
);
When encrypted: true is set globally, every collection is encrypted automatically — no need to set encrypted on each one.
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();
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 |
collection(name, encrypted: true) |
Get an encrypted collection (AES-256-GCM) |
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 |
encrypted |
bool |
false |
Encrypt ALL collections (AES-256-GCM) |
enableCache |
bool |
true |
Enable L1/L2 local cache |
Collection
| Method | Description |
|---|---|
doc(id) |
Document reference |
set(id, data) |
Write with auto-tags for indexed fields |
bulkSet(docs) |
Batch write |
bulkGet(ids) |
Batch read |
bulkDelete(ids) |
Batch delete |
where(f, val) |
Query Builder → .get() or .watch() |
watch('id') |
Real-time doc stream |
syncEvents |
Stream of sync events for custom listeners |
isLocal |
Whether the collection is local-only |
dispose() |
Release resources |
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
Libraries
- aquabase
- Aquabase Flutter SDK — encrypted database with real-time sync.
- auth/auth_client
- cache/file_cache
- cache/smart_cache
- collection/collection
- collection/document
- config
- crypto/aes256
- logs/logs_client
- logs/lz4
- serializer/msgpack
- storage/storage_client
- vault/vault
- ws/ws_channel