waffle_db 0.0.2+1
waffle_db: ^0.0.2+1 copied to clipboard
An advanced, local vector database for Flutter, powered by Rust and HNSW graph indexing.
Waffle-DB
An advanced, local vector database for Flutter, powered by Rust and HNSW graph indexing.
Deliver workstation grade vector search, 1536-bit embedding support, and real-time similarity matching in your local apps.
Why? • Key Features • Installation • Basic Usage • Advanced Usage • Contributing
🤔 Why Choose Waffle-DB? #
In AI apps. Your app needs fast similarity search, not just linear scans.
Most local databases in Flutter are designed for standard JSON or SQL data. They lack the mathematical context needed for vector similarity and AI embeddings. A developer needs to index vectors, run k-NN queries in real-time, and scale to 100,000+ embeddings without UI stutter. Pure Dart vector parsers struggle with the sheer size of 1536-d volumetric data, leading to memory crashes and frozen screens.
📊 How we compare #
| Feature | Standard Local DB | Pure Dart Vector DB | Waffle-DB |
|---|---|---|---|
| Parsing Engine | SQLite / Hive | Dart | 🚀 High-Perf Rust Native Core |
| Indexing Precision | Linear Scan | Linear / LSH | ✅ HNSW Graph (Native) |
| Memory Efficiency | 🔴 High | 🔴 High | 🔋 Zero-Copy FFI Buffers |
| UI Responsiveness | ✅ | ⚠️ | ⚡ Zero UI-Thread Blocking |
| Detailed Metadata | ❌ | ✅ | 🩺 Full Metadata Access |
| OpenAI Ready | ❌ | ❌ | 📈 Native 1536d / 4096d Support |
🛠️ Tech Stack #
- Host Languages: Dart 3.0+ (Flutter) & Rust 1.75+
- Bridge Engine:
flutter_rust_bridgev2.12.0 - Indexing Core:
hnsw_rs(High Performance Hierarchical Navigable Small World graphs) - Persistence Engine:
sled(Embedded key-value transactional database) - Parallel processing:
rayon(data-parallelism library for Rust)
🏎️ Performance Benchmarks #
Waffle-DB is engineered for sub millisecond latencies. Performance measurements executed on an AMD Ryzen 7 5800H processor.
| Database Operation | Average Latency (Microseconds) | Average Latency (Milliseconds) | Real-world Throughput |
|---|---|---|---|
| Database Open & Close | 1,494.1 us | 1.49 ms | ~670 initialization cycles/sec |
| Insert Single Vector (128-dim) | 112.3 us | 0.11 ms | ~8,900 inserts/sec |
| Insert Batch (1,000 vectors) | 27,714.2 us | 27.71 ms | ~36,000 vectors/sec ingested |
| Query KNN (k=10, efSearch=32, no metadata) | 141.6 us | 0.14 ms | ~7,060 similarity queries/sec |
| Query KNN (k=10, efSearch=32, with metadata) | 141.3 us | 0.14 ms | ~7,070 queries/sec (zero overhead retrieval) |
| Get Vector by ID | 14.1 us | 0.01 ms | ~70,000 random reads/sec |
| Get Metadata by ID | 15.5 us | 0.01 ms | ~64,500 random reads/sec |
| Delete Record | 91.4 us | 0.09 ms | ~10,900 deletes/sec |
| Get All IDs (1,000 elements) | 1,144.8 us | 1.14 ms | ~870 scanner runs/sec |
Note
Benchmarks are run inside a Dart test environment calling the Rust engine via native FFI bindings. You can reproduce these measurements by running flutter test test/vector_db_real_world_benchmark_test.dart.
📦 Installation #
Tip
Don't worry about the "Rust Core"! Adding Waffle-DB to your project is designed to be as simple as adding any other Flutter package. While it uses a high-performance Rust engine, you don't need to be a Rust expert or manage complex builds manually. You just install the language once, and the library handles all the heavy lifting, compiling itself automatically for whatever platform (Android, iOS, macOS, Windows, Linux) or architecture you are targeting.
1. Prerequisites (The Rust Toolchain) #
Since this library uses a high-speed bridge to connect Flutter and Rust, you need the Rust compiler installed on your development machine.
- Windows: Download and run rustup-init.exe.
- macOS / Linux: Run the following command in your terminal:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Important
Once Rust is installed, the build system will automatically detect your Flutter target and compile the Rust core into a high-performance native shared library. You only need to set this up once!
2. Add the Dependency #
Add the package to your pubspec.yaml:
dependencies:
waffle_db: ^0.0.1
🚀 Basic Usage #
1. Initialization #
Initialize the library in your main() function before starting the app.
import 'package:flutter/material.dart';
import 'package:waffle_db/waffle_db.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load the native Rust binary into memory
await WaffleDB.init();
runApp(const MyApp());
}
2. Configuration & Opening the Database #
For a great Developer Experience (DX), WaffleDB comes with built-in configuration profiles tailored to common needs:
| Profile | Description & Tuning Details |
|---|---|
mobileProfile |
Designed for constrained devices. Uses quantization to save memory, a smaller 16MB cache, lighter graph parameters (m: 12), and limits to 50k elements to keep the UI smooth. |
serverProfile |
Built for robust backend deployments. Disables quantization for maximum precision, uses a 512MB cache, deeper graph parameters (m: 32, ef_search: 64), and scales up to 5M elements. |
readHeavyProfile |
Optimized for high-recall queries. Maximizes graph search parameters (ef_construction: 200, ef_search: 128), uses a 2GB cache, uses quantization, and utilizes all CPU cores for up to 10M elements. |
writeHeavyProfile |
Perfect for rapid bulk ingestion. Lowers graph construction complexity (ef_construction: 32) to speed up writes, skips quantization overhead, and handles up to 20M elements. |
Configure and open your high-speed vector store using a profile (or customize with the copyWith extension):
import 'dart:typed_data';
import 'package:waffle_db/waffle_db.dart';
import 'package:path_provider/path_provider.dart';
Future<void> runVectorStore() async {
final dir = await getApplicationDocumentsDirectory();
final dbPath = '${dir.path}/my_vectors';
// For a great Developer Experience (DX), WaffleDB comes with built-in
// configuration profiles tailored to common needs:
// Example 1: Use a pre-optimized mobile profile
var config = await WaffleConfig.mobileProfile(
path: dbPath,
dimension: 128, // Your vector dimension
);
// Example 2: You can also use other profiles like `readHeavyProfile`,
// `writeHeavyProfile`, or `serverProfile`.
// Need to tweak a specific setting? Use the `copyWith` extension!
config = await WaffleConfig.readHeavyProfile(
path: dbPath,
dimension: 1536, // OpenAI dimensions
).copyWith(
workerThreads: 8, // Tweak only what you need
cacheSizeBytes: BigInt.from(128 * 1024 * 1024),
);
// Alternatively, you can define everything manually:
/*
final manualConfig = WaffleConfig(
dimension: 128,
path: dbPath,
graphConfig: const WaffleGraphConfig(
m: 16,
metric: WaffleMetric.cosine,
efConstruction: 64,
efSearch: 32,
),
maxElements: 100000,
useQuantization: false,
cacheSizeBytes: BigInt.from(32 * 1024 * 1024),
workerThreads: 4,
);
*/
final db = await WaffleDatabase.open(config);
}
3. Inserting Data (Single & Batch) #
You can insert vectors individually or in batches. Batch insertion is highly recommended for inserting multiple vectors as it uses Rayon-powered parallel processing.
Single Insert:
await db.insert(
'unique_id_1',
Float32List.fromList([0.1, 0.2, ...]),
metadata: utf8.encode('{"name": "Item 1"}'), // Optional arbitrary bytes
);
Batch Insert:
final records = [
WaffleRecord.fromList(id: 'id_1', vector: [0.1, 0.2, ...]),
WaffleRecord.fromList(id: 'id_2', vector: [0.3, 0.4, ...]),
];
await db.insertBatch(records);
4. Querying & Query Builder #
WaffleDB offers two ways to query data: the basic query method and the fluent WaffleQueryBuilder.
Basic Query:
Returns the k nearest neighbors.
final results = db.query(queryVector, k: 5);
for (var result in results) {
print('Found ${result.id} with distance ${result.distance}');
}
Query Builder (Advanced filtering): The query builder allows more flexible filtering and tuning per-query.
final results = await WaffleQueryBuilder(db)
.withVector(queryVector)
.limit(10) // k nearest neighbors
.efSearch(64) // override efSearch for higher recall
.threshold(0.5) // only return results with distance <= 0.5
.includeMetadata(true) // Set to false to avoid disk I/O and speed up queries
.execute();
Fluent API Methods Reference
| Method | Description |
|---|---|
withVector(Float32List vector) |
Sets the query vector as a Float32List. Required before calling execute(). |
withVectorList(List<double> vector) |
Convenience method to set the query vector from a standard Dart List<double>. |
limit(int n) |
Sets the maximum number of nearest neighbors to return (default is 10). |
threshold(double t) |
Sets a maximum distance threshold. Results with a distance greater than t are excluded. Default is 0.0 (no threshold). |
efSearch(int ef) |
Overrides the efSearch config for this query. Higher values increase accuracy (recall) but reduce speed. Pass 0 to use the default config. |
includeMetadata(bool include) |
Defines whether to fetch metadata (default: true). Disabling this speeds up queries by avoiding disk I/O when only IDs and distances are needed. |
execute() |
Executes the configured query asynchronously and returns a Future<List<WaffleQueryResult>>. |
🔬 Advanced Usage #
Namespacing with WaffleCollection #
If you want to logically separate data (e.g., "users" vs "documents") inside the same database, you can use Collections. They handle ID namespacing (collectionName::id) automatically.
final documents = WaffleCollection(db, 'documents');
// Adding to a collection
await documents.add('doc1', queryVector);
await documents.addBatch([...]);
// Searching exclusively within a collection
final searchResults = await documents.search(queryVector, topK: 5);
Managing Data (CRUD Operations) #
WaffleDB provides full support for managing records beyond basic insert and query.
// Check total elements
final count = db.count();
// Retrieve all IDs
final allIds = db.getAllIds();
// Get the original vector or metadata
final vector = db.getVector('unique_id_1');
final metadata = db.getMetadata('unique_id_1');
// Delete a vector
await db.delete('unique_id_1');
// Force flush all pending writes to disk
await db.flush();
// Close the database safely
await db.close();
Dependency Injection (DI) Architecture #
For enterprise apps, inject a custom VectorStoreService to handle different embedding backends (Local, OpenAI, etc.).
// 1. Define the service with a specific database instance
final service = VectorStoreService(db: myWaffleDBInstance);
// 2. Inject into the controller or BLOC
final controller = SearchController(vectorStore: service);
High-Dimensional Filtering & Metadata #
If you want to filter search results beyond simple distance metrics, you can store structured data alongside your vectors.
// Insert with metadata
await db.insert(
'product-42',
vector,
metadata: utf8.encode(jsonEncode({'category': 'electronics', 'price': 299})),
);
// Query and decode
final results = await db.query(vector, k: 5);
for (var res in results) {
final meta = jsonDecode(utf8.decode(res.metadata));
if (meta['category'] == 'electronics') {
print('Found electronic item: ${res.id}');
}
}
🤝 Contributing #
Contributions are welcome! Here’s how to get started:
- Fork the repository.
- Create a new branch:
git checkout -b feature/YourFeature - Commit your changes:
git commit -m "Add amazing feature" - Push to your branch:
git push origin feature/YourFeature - Open a pull request.
⚖️ License #
This project is dual-licensed:
-
Open Source License: GPL-3.0
- Free to use, modify, and distribute under GPL terms.
- Any distributed modified version must also be GPL-3.0.
-
Commercial License:
- Required for using the library in proprietary / closed-source products.
- Only available from the copyright holder (Mostafa Mahmoud).
- Contact: [mostafasensei106@gmail.com]
See the LICENSE file for full details.
Made with ❤️ by MostafaSensei106