6 releases (breaking)
new 0.5.0 | Jul 13, 2025 |
---|---|
0.4.0 | Jul 13, 2025 |
0.3.0 | Jul 13, 2025 |
0.2.0 | Jul 12, 2025 |
0.1.0 | Feb 20, 2025 |
#435 in Database interfaces
64 downloads per month
170KB
2K
SLoC
๐๏ธ Offline First Core
High-performance LMDB-based local storage library optimized for FFI integration with Flutter and cross-platform applications.
โจ Features
- ๐ LMDB-powered - Battle-tested database engine used by Bitcoin Core and OpenLDAP
- ๐ฑ Flutter-ready - Hot restart compatible FFI interface
- โก High performance - Zero-copy reads and ACID transactions
- ๐ Cross-platform - Works on iOS, Android, Windows, macOS, and Linux
- ๐ฆ Simple API - Only 9 functions to learn
๐ Quick Start
Flutter Integration
import 'dart:ffi';
import 'dart:convert';
// 1. Load the native library
final dylib = DynamicLibrary.open('liboffline_first_core.so');
// 2. Define FFI functions
typedef CreateDbNative = Pointer Function(Pointer<Utf8>);
typedef CreateDb = Pointer Function(Pointer<Utf8>);
final createDb = dylib.lookupFunction<CreateDbNative, CreateDb>('create_db');
// 3. Use the database
final dbPointer = createDb("my_app_database".toNativeUtf8());
final jsonData = jsonEncode({
"id": "user_123",
"hash": "content_hash",
"data": {"name": "John Doe", "email": "john@example.com"}
});
final result = pushData(dbPointer, jsonData.toNativeUtf8());
Rust Direct Usage
use offline_first_core::{AppDbState, LocalDbModel};
use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize database
let db = AppDbState::init("my_database".to_string())?;
// Create and store data
let user = LocalDbModel {
id: "user_123".to_string(),
hash: "content_hash".to_string(),
data: json!({"name": "John Doe", "email": "john@example.com"}),
};
db.push(user)?;
// Retrieve data
let user = db.get_by_id("user_123")?.unwrap();
println!("User: {}", user.data["name"]);
Ok(())
}
๐ API Reference
Core Functions
Function | Rust | FFI | Description |
---|---|---|---|
Initialize | AppDbState::init(name) |
create_db(name) |
Create or open database |
Insert | db.push(model) |
push_data(db, json) |
Add new record |
Get by ID | db.get_by_id(id) |
get_by_id(db, id) |
Retrieve specific record |
Get All | db.get() |
get_all(db) |
Retrieve all records |
Update | db.update(model) |
update_data(db, json) |
Update existing record |
Delete | db.delete_by_id(id) |
delete_by_id(db, id) |
Remove record |
Clear | db.clear_all_records() |
clear_all_records(db) |
Remove all records |
Reset | db.reset_database(name) |
reset_database(db, name) |
Reset database |
Close | db.close_database() |
close_database(db) |
Close connection |
Data Model
pub struct LocalDbModel {
pub id: String, // Unique identifier (cannot be empty)
pub hash: String, // Content hash for versioning
pub data: JsonValue, // Your JSON data
}
๐ฏ Usage Examples
1. User Preferences
use offline_first_core::{AppDbState, LocalDbModel};
use serde_json::json;
fn save_preferences() -> Result<(), Box<dyn std::error::Error>> {
let db = AppDbState::init("user_settings".to_string())?;
let preferences = LocalDbModel {
id: "app_preferences".to_string(),
hash: "v1.0".to_string(),
data: json!({
"theme": "dark",
"language": "en",
"notifications": true
}),
};
db.push(preferences)?;
Ok(())
}
2. Shopping Cart
fn add_to_cart(product_id: &str, quantity: i32) -> Result<(), Box<dyn std::error::Error>> {
let db = AppDbState::init("shopping_cart".to_string())?;
let item = LocalDbModel {
id: product_id.to_string(),
hash: format!("cart_{}", chrono::Utc::now().timestamp()),
data: json!({
"product_id": product_id,
"quantity": quantity,
"price": 29.99
}),
};
db.push(item)?;
Ok(())
}
3. Offline Cache
fn cache_article(article_id: &str, content: &str) -> Result<(), Box<dyn std::error::Error>> {
let db = AppDbState::init("article_cache".to_string())?;
let article = LocalDbModel {
id: article_id.to_string(),
hash: format!("article_{}", md5::compute(content)),
data: json!({
"title": "Article Title",
"content": content,
"cached_at": chrono::Utc::now().to_rfc3339()
}),
};
db.push(article)?;
Ok(())
}
๐ง Advanced Usage
Error Handling
use offline_first_core::{AppDbState, AppResponse};
match db.push(model) {
Ok(_) => println!("โ
Success"),
Err(AppResponse::DatabaseError(msg)) => eprintln!("๐พ Database error: {}", msg),
Err(AppResponse::SerializationError(msg)) => eprintln!("๐ JSON error: {}", msg),
Err(AppResponse::NotFound(msg)) => eprintln!("๐ Not found: {}", msg),
Err(other) => eprintln!("๐ฅ Other error: {}", other),
}
Batch Operations
fn bulk_insert(records: Vec<LocalDbModel>) -> Result<usize, Box<dyn std::error::Error>> {
let db = AppDbState::init("bulk_data".to_string())?;
let mut success_count = 0;
for model in records {
if db.push(model).is_ok() {
success_count += 1;
}
}
Ok(success_count)
}
Hot Restart (Flutter)
class DatabaseManager {
static Pointer? _dbPointer;
static void initDatabase() {
_dbPointer = createDb("my_app_db".toNativeUtf8());
}
static void closeDatabase() {
if (_dbPointer != null) {
closeDatabase(_dbPointer!);
_dbPointer = null;
}
}
}
๐ ๏ธ Setup
Installation
Add to your Cargo.toml
:
[dependencies]
offline_first_core = "0.3.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
For FFI projects:
[lib]
name = "my_storage_lib"
crate-type = ["staticlib", "cdylib"]
Building
# Debug build
cargo build
# Release build
cargo build --release
# Cross-platform builds
cargo build --target aarch64-apple-ios --release # iOS
cargo build --target aarch64-linux-android --release # Android
cargo build --target x86_64-pc-windows-msvc --release # Windows
โ ๏ธ Important Notes
LMDB Limitations
// โ Empty IDs not supported
let invalid = LocalDbModel {
id: "".to_string(), // Will fail!
// ...
};
// โ
Always use non-empty IDs
let valid = LocalDbModel {
id: "user_123".to_string(), // Good!
// ...
};
Memory Safety (FFI)
// โ
Always check null pointers
void* db = create_db("my_db");
if (db == NULL) {
// Handle error
return;
}
// โ
Free returned strings
const char* result = get_by_id(db, "user_1");
if (result != NULL) {
// Use result...
free((void*)result); // Important!
}
Performance Tips
// โ
DO: Reuse connections
let db = AppDbState::init("my_db".to_string())?;
for i in 0..1000 {
db.push(create_model(i))?; // Efficient
}
// โ DON'T: Create new connections
for i in 0..1000 {
let db = AppDbState::init("my_db".to_string())?; // Slow!
db.push(create_model(i))?;
}
๐งช Testing
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_crud() {
let db = AppDbState::init("test_db".to_string()).unwrap();
let model = LocalDbModel {
id: "test_1".to_string(),
hash: "test_hash".to_string(),
data: json!({"name": "Test User"}),
};
// Insert
assert!(db.push(model).is_ok());
// Read
let retrieved = db.get_by_id("test_1").unwrap();
assert!(retrieved.is_some());
// Update
let mut updated = retrieved.unwrap();
updated.data["name"] = json!("Updated User");
assert!(db.update(updated).is_ok());
// Delete
assert!(db.delete_by_id("test_1").unwrap());
}
}
๐ฆ Integration Examples
Flutter Plugin
// pubspec.yaml
dependencies:
ffi: ^2.0.0
// lib/database.dart
import 'dart:ffi';
import 'dart:io';
class NativeDatabase {
late DynamicLibrary _lib;
NativeDatabase() {
if (Platform.isAndroid) {
_lib = DynamicLibrary.open('liboffline_first_core.so');
} else if (Platform.isIOS) {
_lib = DynamicLibrary.process();
}
}
// Define your FFI functions here...
}
React Native
// Install react-native-ffi
npm install react-native-ffi
// Use the library
import { NativeModules } from 'react-native';
const { OfflineFirstCore } = NativeModules;
async function saveData(id, data) {
const result = await OfflineFirstCore.pushData(id, JSON.stringify(data));
return JSON.parse(result);
}
๐ Changelog
v0.5.0 - TEST
- Update documentation
v0.4.0 - TEST
- Improve test cases
v0.3.0 - LMDB Migration
- โจ Migrated from redb to LMDB
- โจ Added
close_database()
function - ๐ Fixed Flutter hot restart issues
- ๐ง Improved error handling
- ๐ Added comprehensive test suite (60+ tests)
v0.2.0 - Feature Expansion
- โจ Added
clear_all_records()
andreset_database()
- ๐ง Improved error handling
- ๐ก๏ธ Enhanced FFI safety
v0.1.0 - Initial Release
- โจ Basic CRUD operations
- ๐ FFI interface for cross-platform integration
๐ค Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
Made with โค๏ธ for developers who need reliable offline storage
โญ Star on GitHub โข ๐ฆ View on Crates.io โข ๐ Read the Docs
Dependencies
~1.2โ2.5MB
~54K SLoC