β‘ VexStorage
The ultimate Flutter/Dart storage solution.
Zero external dependencies. Blazing fast. SQL-like queries. Reactive streams. AES-256 encryption.
One package to replace them all.
Why VexStorage?
Every existing storage package for Flutter solves part of the puzzle:
| Need | Package | Problem |
|---|---|---|
| Simple key-value | GetStorage | No queries, no types, no encryption |
| Typed objects | Hive | Requires code generation, no queries |
| SQL queries | sqflite | Platform-specific, verbose API, no encryption |
| All of the above | β Nothing | Until now |
VexStorage combines the best of all three β and adds features none of them have.
π Package Comparison
| Feature | VexStorage | GetStorage | Hive | sqflite |
|---|---|---|---|---|
| Zero external dependencies | β | β | β (path_provider) | β (native plugins) |
| No code generation | β | β | β (build_runner) | β |
| Pure Dart (no native code) | β | β | β | β |
| Works on all platforms | β | β | β | β (no web/desktop) |
| Simple key-value API | β | β | β | β |
| Typed documents / objects | β | β | β | β |
| SQL-like query engine | β | β | β | β |
| Dot-notation nested fields | β | β | β | β |
| Fluent query builder | β | β | β | β |
| B-tree indexes | β | β | β | β |
| Reactive streams (watch) | β | β | β | β |
| Watch a single document | β | β | β | β |
| Watch a live query | β | β | β | β |
| AES-256 encryption (built-in) | β | β | β | β |
| Write-Ahead Log (crash safety) | β | β | β | β |
| Atomic batch writes | β | β | β | β |
| Automatic compaction | β | β | β | β |
| Schema migrations | β | β | β | β |
| Aggregation (sum/avg/min/max) | β | β | β | β |
| groupBy | β | β | β | β |
| DateTime / Uint8List types | β | β | β | β |
| Export / Import | β | β | β | β |
| In-memory adapter (for tests) | β | β | β | β |
| Custom storage adapter | β | β | β | β |
| Pagination (page/limit/offset) | β | β | β | β |
| Field projection (select) | β | β | β | β |
| OR / AND filter groups | β | β | β | β |
| Regex / startsWith / endsWith | β | β | β | β |
| Null-safe field exists check | β | β | β | β |
What VexStorage fixes from each package
vs GetStorage
- β Full query engine β not just key-value
- β Typed documents with nested field support
- β AES-256 encryption (GetStorage has none)
- β
Reactive streams β GetStorage has no
watch() - β Crash-safe WAL (GetStorage can corrupt on crash)
vs Hive
- β
No code generation β no
build_runner, no@HiveType, no adapters to write - β Full SQL-like query engine β Hive can only filter with custom code
- β
Built-in encryption (Hive requires a separate
hive_cipher) - β Aggregation, groupBy, projections β Hive has none
- β Native-code-free β Hive uses native binary format
vs sqflite
- β Works on all platforms including Web and macOS β sqflite doesn't
- β No SQL strings β type-safe fluent query builder, no injection risks
- β
Schema-free β no
CREATE TABLE, noALTER TABLEever needed - β Built-in encryption β sqflite requires SQLCipher (complex setup)
- β
Reactive β sqflite has no
watch()or live queries - β
Simple setup β
Vex.init()vs configuring SQLite migrations manually
Installation
dependencies:
vex_storage: ^1.0.0
flutter pub get
No other steps. No native setup. No build_runner. No code generation. Just install and use.
Quick Start
1. Initialise
import 'package:vex_storage/vex_storage.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final dir = await getApplicationDocumentsDirectory();
await Vex.init(
options: VexOptions(directory: dir.path),
);
runApp(MyApp());
}
2. Open a collection
final users = await Vex.collection('users');
3. Store data
// Insert a document (auto-id)
final doc = await users.put({
'name': 'Alice',
'age': 30,
'address': {'city': 'London', 'zip': 'EC1A'},
});
// Insert with your own id
await users.putWithId('user_001', {'name': 'Bob', 'age': 25});
// Simple key-value (like GetStorage)
await users.write('lastSync', DateTime.now());
final lastSync = users.read<DateTime>('lastSync');
4. Query data
// Fluent query builder
final results = users
.query()
.where('age').gte(18)
.and('address.city').eq('London') // β dot-notation nested fields
.sortBy('name')
.limit(20)
.offset(0)
.findAll();
// OR groups
final adminsOrMods = users.query().or([
VexFilter.eq('role', 'admin'),
VexFilter.eq('role', 'moderator'),
]).findAll();
// Pagination
final page2 = users.query().sortBy('name').page(1, pageSize: 20).findAll();
// Count
final londonCount = users.query().where('city').eq('London').count();
// Exists check
final hasAdmin = users.query().where('role').eq('admin').exists();
5. Update & Delete
// Merge updates (keeps existing fields)
await users.update(doc.id, {'age': 31, 'verified': true});
// Upsert (insert or update)
await users.upsert('user_001', {'name': 'Bob', 'age': 26});
// Delete one
await users.delete(doc.id);
// Delete all
await users.clear();
6. Reactive Streams
// Watch all changes
users.watch().listen((event) {
print('Changed: ${event.type}');
});
// Watch a single document
users.watchDocument(doc.id).listen((updatedDoc) {
setState(() => _doc = updatedDoc);
});
// Watch a live query β re-runs automatically on any change
users
.watchQuery(() => users.query().where('active').eq(true).sortBy('name'))
.listen((activeDocs) {
setState(() => _activeUsers = activeDocs);
});
7. Aggregation
final total = users.sumOf('score');
final avg = users.avgOf('score');
final oldest = users.maxOf('age');
final youngest = users.minOf('age');
// Group by field
final byCity = users.groupBy('city');
// β {'London': [...], 'Paris': [...]}
8. Indexes (for large datasets)
// Create once β automatically kept in sync
users.createIndex('email');
users.createIndex('age', order: VexIndexOrder.descending);
// Queries on indexed fields are O(log n) instead of O(n)
final alice = users.query().where('email').eq('alice@example.com').findFirst();
9. Encryption
await Vex.init(
options: VexOptions(
directory: dir.path,
cipher: VexCipher.fromPassword('my-super-secret'),
// Or from raw bytes:
// cipher: VexCipher.fromKey(myKeyBytes),
),
);
// Everything stored on disk is now AES-256 encrypted β no extra steps.
10. Schema Migrations
final migrator = VexMigrator(currentVersion: 3)
..addMigration(0, (doc) => doc.merge({'role': 'user'}))
..addMigration(1, (doc) => doc.set('settings.theme', 'light'))
..addMigration(2, (doc) => doc.merge({'emailVerified': false}));
await users.migrate(migrator);
11. Testing (in-memory)
setUp(() async {
await Vex.init(options: const VexOptions(inMemory: true));
col = await Vex.collection('test');
});
tearDown(() async {
await Vex.closeAll();
});
No disk I/O. No cleanup. Tests run in microseconds.
API Reference
Vex (store)
| Method | Description |
|---|---|
Vex.init({options}) |
Initialise once at app start |
Vex.collection(name) |
Open / get a collection |
Vex.of(name) |
Get an already-open collection |
Vex.closeAll() |
Flush & close everything |
Vex.deleteCollection(name) |
Delete files for a collection |
VexCollection
| Method | Description |
|---|---|
put(data) |
Insert with auto-id β VexDocument |
putWithId(id, data) |
Insert / replace by id |
update(id, updates) |
Merge updates into existing doc |
upsert(id, data) |
Insert or update |
get(id) |
Get one document or null |
getAll() |
Get all documents |
delete(id) |
Delete by id β bool |
clear() |
Delete all documents |
putMany(list) |
Batch insert |
write(key, value) |
Key-value write |
read<T>(key) |
Key-value read |
remove(key) |
Key-value delete |
hasKey(key) |
Key-value existence |
query() |
Start a fluent query |
executeQuery(query) |
Run a pre-built VexQuery |
watch() |
Stream of all changes |
watchDocument(id) |
Stream of one document |
watchQuery(builder) |
Live query stream |
createIndex(field) |
Create a field index |
sumOf / avgOf / minOf / maxOf |
Aggregation |
groupBy(field) |
Group documents |
migrate(migrator) |
Run schema migrations |
export() / import(data) |
Backup and restore |
compact() |
Force WAL compaction |
VexQueryBuilder
| Method | Description |
|---|---|
.where(field).eq(v) |
Equality filter |
.where(field).neq(v) |
Inequality |
.where(field).gt/gte/lt/lte(v) |
Numeric/string comparison |
.where(field).between(a, b) |
Range filter |
.where(field).inList([...]) |
Membership test |
.where(field).contains(s) |
Substring match |
.where(field).startsWith(s) |
Prefix match |
.where(field).endsWith(s) |
Suffix match |
.where(field).matches(regex) |
Regex match |
.where(field).exists() |
Null check |
.or([filters]) |
OR filter group |
.sortBy(field) |
Sort ascending |
.sortByDesc(field) |
Sort descending |
.limit(n) |
Take first n results |
.offset(n) |
Skip first n results |
.page(n, pageSize: 20) |
Page-based pagination |
.select([fields]) |
Field projection |
.findAll() |
Execute β List |
.findFirst() |
Execute β first or null |
.count() |
Execute β int |
.exists() |
Execute β bool |
Architecture
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VexStorage β
β β
β βββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββ β
β β Vex β β VexCollection β β
β β (singleton) ββββ CRUD Β· KV Β· Query Β· Aggregation Β· Watchβ β
β βββββββββββββββ βββββββββββββββββββββ¬ββββββββββββββββββββββ β
β β β
β βββββββββββββΌβββββββββββ β
β β VexEngine β β
β β βββββββββββββββββββ β β
β β β In-Memory Map β β β
β β β (O(1) reads) β β β
β β ββββββββββ¬βββββββββ β β
β β ββββββββββΌβββββββββ β β
β β β VexIndex(es) β β β
β β β (O(log n)) β β β
β β ββββββββββ¬βββββββββ β β
β β ββββββββββΌβββββββββ β β
β β β WAL Buffer β β β
β β β (50ms debounce) β β β
β β ββββββββββ¬βββββββββ β β
β βββββββββββββΌββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββΌββββββββββββββββββββββ β
β β VexAdapter β β
β β VexFileAdapter VexMemoryAdapter β β
β β (snapshot + WAL) (tests / cache) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β VexCipher (AES-256 CTR, pure Dart, optional) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Performance characteristics
| Operation | Complexity | Notes |
|---|---|---|
get(id) |
O(1) | HashMap lookup in memory |
put(doc) |
O(1) amortised | Write batched to WAL |
delete(id) |
O(1) | In-memory remove + WAL |
| Query (no index) | O(n) | Full in-memory scan β very fast |
| Query (with index, eq) | O(log n) | Binary search on sorted index |
putMany(n docs) |
O(n) | Single atomic batch |
| Compaction | O(n) | Runs off the main thread trigger |
Platform Support
| Platform | Supported | Notes |
|---|---|---|
| Android | β | Provide directory via path_provider |
| iOS | β | Provide directory via path_provider |
| macOS | β | Auto-creates .vex_data/ in CWD |
| Windows | β | Auto-creates .vex_data/ in CWD |
| Linux | β | Auto-creates .vex_data/ in CWD |
| Web | β | Use inMemory: true |
FAQ
Q: Do I need to run build_runner?
No. VexStorage requires zero code generation. No annotations, no generated files.
Q: Does it work on Flutter Web?
Yes β use VexOptions(inMemory: true) for web. Persistent web storage via IndexedDB is on the roadmap.
Q: Is it safe to call put() from multiple isolates?
The engine is single-isolate by design (like Hive). For multi-isolate use, open separate collections.
Q: How does encryption affect performance?
AES-256 in CTR mode is very fast in pure Dart. For large datasets, benchmarks show < 5% overhead vs unencrypted.
Q: Can I store custom model classes?
Yes β use VexSerializer.objectToMap(model) to convert any class that implements toJson().
License
MIT Β© 2025 Mysterious Coder
Libraries
- vex_storage
- VexStorage β The ultimate Flutter/Dart storage solution.