stashbox 0.1.0
stashbox: ^0.1.0 copied to clipboard
A lightweight, persistent JSON storage solution for Dart and Flutter applications
StashBox #
A lightweight, persistent JSON storage solution for Dart and Flutter applications
Features #
- Persistent JSON Storage: Store and retrieve data in JSON format
- Lazy Loading: Efficient handling of large files with on-demand loading
- Transaction Support: Group operations with commit/rollback capabilities
- Caching: Configurable in-memory caching for improved performance
- Type Safety: Get typed values with default fallbacks
- Object Serialization: Helpers for storing complex objects
- Batch Operations: Perform multiple operations efficiently
- Error Handling: Specialized exceptions for better error reporting
- File Integrity: Automatic backup creation for corrupted files
- Enhanced File Operations: Retry logic, file locking, and concurrent access protection
- Backup & Restore: Full backup and restore functionality with pre-restore backups
- Watch API: Real-time change notifications with event streaming
Getting Started #
Add the package to your pubspec.yaml:
dependencies:
stashbox: ^0.1.0
Usage #
Basic Operations #
import 'package:stashbox/stashbox.dart';
// Create a StashBox with a file path
final store = StashBox('data/mystore.json');
// Enable caching for better performance
store.configureCaching(enabled: true, maxSize: 100);
// Simple operations
await store.set('username', 'spicy_katsu');
await store.set('age', 25);
await store.set('isActive', true);
// Retrieve values
final username = await store.get<String>('username');
final age = await store.getTyped<int>('age', 0); // With default value
final isActive = await store.getRequired<bool>('isActive'); // Throws if not found
// Check if a key exists
if (await store.has('username')) {
print('Username exists!');
}
// Remove a value
await store.remove('age');
// Clear all data
await store.clear();
Lazy Loading #
For large datasets, enable lazy loading to keep memory usage low:
// Create a StashBox with lazy loading enabled
final store = StashBox(
'data/large_dataset.json',
lazyLoadConfig: LazyLoadConfig(
enabled: true,
maxMemoryItems: 1000, // Max items to keep in memory
fileSizeThreshold: 10 * 1024 * 1024, // 10MB file size threshold
),
);
// Only accessed items are loaded into memory
final value = await store.get('some_key'); // Loads from disk if needed
// Check if lazy mode is active
print('Is lazy mode: ${store.isLazyMode}');
print('Items in memory: ${store.memoryItemCount}');
Working with Objects #
class User {
final String name;
final int age;
User(this.name, this.age);
// Convert User to JSON
Map<String, dynamic> toJson() => {
'name': name,
'age': age,
};
// Create User from JSON
static User fromJson(Map<String, dynamic> json) =>
User(json['name'], json['age']);
}
// Store an object
final user = User('Alice', 30);
await store.setObject('user1', user, (u) => u.toJson());
// Retrieve an object
final retrievedUser = await store.getObject('user1', User.fromJson);
// Store a list of objects
final users = [User('Alice', 30), User('Bob', 25)];
await store.setObjectList('users', users, (u) => u.toJson());
// Retrieve a list of objects
final retrievedUsers = await store.getObjectList('users', User.fromJson);
Transactions #
// Using the transaction helper
final result = await store.transaction(() async {
await store.set('counter', 1);
await store.set('message', 'Transaction in progress');
return true;
});
// Manual transaction control
await store.beginTransaction();
try {
await store.set('key1', 'value1');
await store.set('key2', 'value2');
await store.commitTransaction();
} catch (e) {
store.rollbackTransaction();
rethrow;
}
// Nested transactions are supported
await store.transaction(() async {
await store.set('outer', 'value');
await store.transaction(() async {
await store.set('inner', 'value');
// This can be rolled back independently
});
});
Batch Operations #
// Perform multiple operations with a single save
await store.batch(() async {
await store.set('key1', 'value1');
await store.set('key2', 'value2');
await store.set('key3', 'value3');
});
// Update multiple values at once
await store.update({
'name': 'John',
'age': 30,
'active': true,
});
Backup and Restore #
// Create a backup (automatically timestamped)
final backupPath = await store.backup();
print('Backup created at: $backupPath');
// Create a backup at a specific location
final customBackup = File('data/my_backup.json');
await store.backup(destination: customBackup);
// Restore from backup
final backupFile = File(backupPath!);
final success = await store.restore(backupFile);
// Restore and replace the current file
await store.restore(backupFile, replaceCurrentFile: true);
// Note: This automatically creates a pre-restore backup
Caching #
// Enable caching with custom size
store.configureCaching(enabled: true, maxSize: 200);
// Cache is automatically used for get operations
await store.get('cached_key'); // First time loads from file
await store.get('cached_key'); // Second time loads from cache
// Invalidate specific keys
store.invalidateCache(['key1', 'key2']);
// Clear entire cache
store.invalidateCache();
Watch API (NEW!) #
Monitor your data store for real-time changes:
// Watch all changes
final subscription = store.watchAll();
subscription.stream.listen((event) {
print('Event type: ${event.type}');
print('Key: ${event.key}');
print('Value: ${event.value}');
print('Old value: ${event.oldValue}');
print('Timestamp: ${event.timestamp}');
});
// Watch specific keys
final keySubscription = store.watchKeys(['username', 'email']);
keySubscription.stream.listen((event) {
if (event.type == StashBoxEventType.update) {
print('Key ${event.key} changed from ${event.oldValue} to ${event.value}');
}
});
// Watch a single key
final singleSub = store.watchKey('counter');
singleSub.stream.listen((event) {
print('Counter changed: ${event.value}');
});
// Event types
// - StashBoxEventType.update: Key added or updated
// - StashBoxEventType.remove: Key removed
// - StashBoxEventType.clear: All data cleared
// - StashBoxEventType.load: Data loaded from file
// - StashBoxEventType.save: Data saved to file
// Cancel subscriptions
await subscription.cancel();
await store.cancelAllWatches();
// Check number of active subscriptions
print('Active watchers: ${store.watchCount}');
Error Handling #
StashBox provides specialized exceptions for better error handling:
try {
await store.get('some_key');
} on StashBoxFileException catch (e) {
print('File operation failed: ${e.message}');
print('Original error: ${e.innerError}');
} on StashBoxKeyException catch (e) {
print('Key operation failed: ${e.message}');
print('Problematic key: ${e.key}');
} on StashBoxTransactionException catch (e) {
print('Transaction failed: ${e.message}');
} on StashBoxException catch (e) {
print('General StashBox error: ${e.message}');
}
File Integrity #
// Verify file integrity (creates backup if corrupted)
final isValid = await store.verifyIntegrity();
if (!isValid) {
print('File was corrupted, backup created');
}
// StashBox automatically handles corrupted files:
// - Creates a backup with timestamp
// - Resets to empty state
// - Throws StashBoxFileException
Advanced Configuration #
Lazy Loading Configuration #
final store = StashBox(
'data/huge_dataset.json',
lazyLoadConfig: LazyLoadConfig(
enabled: true,
maxMemoryItems: 500, // Keep only 500 items in memory
fileSizeThreshold: 5 * 1024 * 1024, // Enable for files > 5MB
),
);
Auto-Save Control #
// Disable auto-save for better performance during bulk operations
store.setAutoSave(false);
// Perform many operations
for (int i = 0; i < 1000; i++) {
await store.set('key_$i', 'value_$i');
}
// Manually save when done
await store.saveToFile();
// Re-enable auto-save
store.setAutoSave(true);
Performance Tips #
- Use Lazy Loading for files larger than 10MB
- Enable Caching for frequently accessed data
- Use Batch Operations for multiple changes
- Disable Auto-Save during bulk operations
- Use Transactions for atomic operations
License #
This project is licensed under the MIT License - see the LICENSE file for details.