DartDB

pub package License: MIT

A high-performance embedded key-value database for pure Dart backend applications. Built on LMDB with Rust FFI for maximum performance and data persistence.

πŸš€ Features

  • Embedded Database: No external database server required
  • High Performance: Built on LMDB (Lightning Memory-Mapped Database)
  • Persistent Storage: Data survives application restarts
  • Redis-like API: Familiar interface for key-value operations
  • Pure Dart: Designed specifically for Dart backend applications
  • Cross Platform: Works on Linux, macOS, and Windows
  • ACID Transactions: Full transaction support
  • Memory Efficient: Memory-mapped files for optimal performance
  • Rust FFI: Native performance through Rust integration

🎯 Use Cases

Perfect for backend applications that need:

  • Caching Layer: Fast in-memory cache with persistence
  • Session Storage: User session data with reliability
  • Configuration Storage: Application settings and metadata
  • Queue Management: Job queues and task scheduling
  • Metrics Collection: Performance metrics and analytics
  • Embedded Analytics: Local data processing and aggregation

πŸ“¦ Installation

Add this to your pubspec.yaml:

dependencies:
  dart_db: ^0.1.0

Then run:

dart pub get

πŸ”§ Quick Start

import 'package:dart_db/dart_db.dart';

void main() async {
  // Initialize the database
  final db = await DartDB.open('my_database');
  
  // Store data
  await db.set('user:1', {'name': 'John', 'email': 'john@example.com'});
  await db.set('counter', 42);
  
  // Retrieve data
  final user = await db.get('user:1');
  final counter = await db.get<int>('counter');
  
  print('User: $user');
  print('Counter: $counter');
  
  // Check if key exists
  final exists = await db.exists('user:1');
  print('User exists: $exists');
  
  // Delete data
  await db.delete('counter');
  
  // Close the database
  await db.close();
}

πŸ“– API Reference

Database Operations

Opening a Database

// Open with default options
final db = await DartDB.open('database_name');

// Open with custom options
final db = await DartDB.open(
  'database_name',
  options: DatabaseOptions(
    maxSize: 1024 * 1024 * 100, // 100MB
    readOnly: false,
    createIfNotExists: true,
  ),
);

Basic Operations

// Set a value
await db.set('key', 'value');
await db.set('user:123', {'name': 'Alice', 'age': 30});

// Get a value
final value = await db.get('key');
final user = await db.get<Map>('user:123');

// Get with default value
final count = await db.get('count', defaultValue: 0);

// Check if key exists
final exists = await db.exists('key');

// Delete a key
await db.delete('key');

// Get all keys
final keys = await db.keys();

// Get keys with pattern
final userKeys = await db.keys(pattern: 'user:*');

Batch Operations

// Set multiple values at once
await db.setMultiple({
  'key1': 'value1',
  'key2': 'value2',
  'key3': 'value3',
});

// Get multiple values at once
final values = await db.getMultiple(['key1', 'key2', 'key3']);

// Delete multiple keys
await db.deleteMultiple(['key1', 'key2', 'key3']);

Advanced Operations

// Increment a numeric value
await db.increment('counter', by: 5);

// Decrement a numeric value
await db.decrement('counter', by: 2);

// Set with expiration (TTL)
await db.setWithTTL('session:abc', sessionData, Duration(hours: 2));

// Get database statistics
final stats = await db.stats();
print('Total keys: ${stats.keyCount}');
print('Database size: ${stats.sizeBytes}');

Transactions

// Execute operations in a transaction
await db.transaction((txn) async {
  await txn.set('account:1', 100);
  await txn.set('account:2', 200);
  final total = await txn.get<int>('account:1') + await txn.get<int>('account:2');
  await txn.set('total', total);
});

// Read-only transaction
final result = await db.readTransaction((txn) async {
  final user1 = await txn.get('user:1');
  final user2 = await txn.get('user:2');
  return [user1, user2];
});

Data Types Support

DartDB supports all JSON-serializable Dart types:

// Primitives
await db.set('string', 'Hello World');
await db.set('number', 42);
await db.set('double', 3.14159);
await db.set('boolean', true);

// Collections
await db.set('list', [1, 2, 3, 4, 5]);
await db.set('map', {'key': 'value', 'number': 123});

// Complex objects (must be JSON-serializable)
await db.set('user', {
  'id': 123,
  'name': 'Alice',
  'preferences': {
    'theme': 'dark',
    'notifications': true,
  },
  'tags': ['admin', 'premium'],
});

⚑ Performance

DartDB is built on LMDB, one of the fastest embedded databases available:

  • Read Performance: Up to 1M+ reads per second
  • Write Performance: Up to 100K+ writes per second
  • Memory Efficient: Uses memory-mapped files
  • Crash Safe: ACID transactions ensure data integrity
  • Concurrent Access: Multiple readers, single writer

Benchmarks

Operation    | Operations/sec | Latency (avg)
-------------|----------------|-------------
GET          | 1,200,000      | 0.8ΞΌs
SET          | 150,000        | 6.7ΞΌs
DELETE       | 180,000        | 5.6ΞΌs
BATCH SET    | 300,000        | 3.3ΞΌs
TRANSACTION  | 80,000         | 12.5ΞΌs

πŸ”§ Configuration

Database Options

final options = DatabaseOptions(
  // Maximum database size (default: 100MB)
  maxSize: 1024 * 1024 * 500, // 500MB
  
  // Open database in read-only mode
  readOnly: false,
  
  // Create database if it doesn't exist
  createIfNotExists: true,
  
  // Number of readers allowed
  maxReaders: 126,
  
  // Sync mode for durability
  syncMode: SyncMode.full, // full, lazy, none
  
  // Compression settings
  compression: CompressionOptions(
    enabled: true,
    algorithm: CompressionAlgorithm.lz4,
    level: 1,
  ),
);

final db = await DartDB.open('database', options: options);

Environment Variables

# Default database directory
export DART_DB_PATH=/var/lib/dart_db

# Default maximum size
export DART_DB_MAX_SIZE=104857600

# Log level
export DART_DB_LOG_LEVEL=info

πŸ—οΈ Architecture

DartDB uses a layered architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Dart API      β”‚  ← High-level Dart interface
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   FFI Bridge    β”‚  ← Dart ↔ Rust communication
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   Rust Core     β”‚  ← Core database logic
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   LMDB Engine   β”‚  ← Lightning Memory-Mapped DB
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Components

  • Dart API: High-level, type-safe interface for Dart applications
  • FFI Bridge: Efficient communication layer between Dart and Rust
  • Rust Core: Core database operations, serialization, and error handling
  • LMDB Engine: Battle-tested, high-performance storage engine

πŸ“š Examples

Web API Cache

import 'package:dart_db/dart_db.dart';

class ApiCache {
  late DartDB _db;
  
  Future<void> init() async {
    _db = await DartDB.open('api_cache');
  }
  
  Future<Map<String, dynamic>?> getCachedResponse(String endpoint) async {
    return await _db.get<Map<String, dynamic>>(endpoint);
  }
  
  Future<void> cacheResponse(
    String endpoint, 
    Map<String, dynamic> response,
    {Duration? ttl}
  ) async {
    if (ttl != null) {
      await _db.setWithTTL(endpoint, response, ttl);
    } else {
      await _db.set(endpoint, response);
    }
  }
}

Session Store

import 'package:dart_db/dart_db.dart';

class SessionStore {
  late DartDB _db;
  
  Future<void> init() async {
    _db = await DartDB.open('sessions');
  }
  
  Future<void> createSession(String sessionId, Map<String, dynamic> data) async {
    await _db.setWithTTL(
      'session:$sessionId',
      {
        ...data,
        'created': DateTime.now().toIso8601String(),
        'lastAccessed': DateTime.now().toIso8601String(),
      },
      Duration(hours: 24),
    );
  }
  
  Future<Map<String, dynamic>?> getSession(String sessionId) async {
    final session = await _db.get<Map<String, dynamic>>('session:$sessionId');
    if (session != null) {
      // Update last accessed time
      session['lastAccessed'] = DateTime.now().toIso8601String();
      await _db.setWithTTL('session:$sessionId', session, Duration(hours: 24));
    }
    return session;
  }
  
  Future<void> destroySession(String sessionId) async {
    await _db.delete('session:$sessionId');
  }
}

Configuration Manager

import 'package:dart_db/dart_db.dart';

class ConfigManager {
  late DartDB _db;
  
  Future<void> init() async {
    _db = await DartDB.open('app_config');
  }
  
  Future<T?> getConfig<T>(String key, {T? defaultValue}) async {
    return await _db.get<T>(key, defaultValue: defaultValue);
  }
  
  Future<void> setConfig<T>(String key, T value) async {
    await _db.set(key, value);
  }
  
  Future<Map<String, dynamic>> getAllConfig() async {
    final keys = await _db.keys();
    final configs = <String, dynamic>{};
    
    for (final key in keys) {
      configs[key] = await _db.get(key);
    }
    
    return configs;
  }
}

πŸ§ͺ Testing

import 'package:test/test.dart';
import 'package:dart_db/dart_db.dart';

void main() {
  group('DartDB Tests', () {
    late DartDB db;
    
    setUp(() async {
      // Use in-memory database for testing
      db = await DartDB.open(':memory:');
    });
    
    tearDown(() async {
      await db.close();
    });
    
    test('should store and retrieve values', () async {
      await db.set('test_key', 'test_value');
      final value = await db.get('test_key');
      expect(value, equals('test_value'));
    });
    
    test('should handle complex objects', () async {
      final complexObject = {
        'id': 123,
        'name': 'Test User',
        'preferences': ['dark_mode', 'notifications'],
      };
      
      await db.set('user', complexObject);
      final retrieved = await db.get<Map>('user');
      expect(retrieved, equals(complexObject));
    });
  });
}

πŸ” Troubleshooting

Common Issues

Database won't open

Error: Failed to open database
  • Check file permissions on the database directory
  • Ensure sufficient disk space
  • Verify the database path is valid

Out of memory errors

Error: Out of memory
  • Increase maxSize in DatabaseOptions
  • Check available system memory
  • Consider using compression

Performance issues

Slow read/write operations
  • Use batch operations for multiple keys
  • Enable compression for large values
  • Consider using transactions for related operations

Debug Mode

Enable debug logging:

DartDB.setLogLevel(LogLevel.debug);

Memory Usage

Monitor memory usage:

final stats = await db.stats();
print('Memory usage: ${stats.memoryUsage} bytes');
print('Disk usage: ${stats.diskUsage} bytes');

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

  1. Clone the repository
  2. Install Rust (for FFI compilation)
  3. Run dart pub get
  4. Run tests with dart test

Building

# Build Rust FFI library
cargo build --release

# Run Dart tests
dart test

# Format code
dart format .

# Analyze code
dart analyze

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • LMDB - Lightning Memory-Mapped Database
  • Dart FFI - Foreign Function Interface
  • Rust - Systems programming language

Built with ❀️ for the Dart community by JhonaCode

Libraries

dart_db
Support for doing something awesome.