#flutter #lmdb #offline #local-storage #memory-safety

offline_first_core

High-performance LMDB-based local storage library optimized for FFI integration with Flutter and cross-platform applications

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

Download history 149/week @ 2025-04-20 9/week @ 2025-04-27 3/week @ 2025-05-04 9/week @ 2025-05-11 3/week @ 2025-05-18 61/week @ 2025-07-06

64 downloads per month

MIT license

170KB
2K SLoC

๐Ÿ—ƒ๏ธ Offline First Core

Crates.io Documentation License: MIT

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() and reset_database()
  • ๐Ÿ”ง Improved error handling
  • ๐Ÿ›ก๏ธ Enhanced FFI safety

v0.1.0 - Initial Release

  • โœจ Basic CRUD operations
  • ๐Ÿ”Œ FFI interface for cross-platform integration

๐Ÿค Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. 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