work_db 1.1.0
work_db: ^1.1.0 copied to clipboard
A lightweight, cross-platform local database for Dart and Flutter. Simple key-value storage with collections, supporting Desktop, Web, and Mobile.
work_db #
A lightweight, cross-platform local database for Dart and Flutter with optional thread-safe locking. Simple key-value storage organized in collections, supporting Desktop, Web, Mobile platforms, and concurrent access scenarios.
Features #
- Cross-platform: Windows, macOS, Linux, Web, iOS, Android
- Simple API: CRUD operations, batch, upsert
- Collections: Organize data in named collections
- Batch operations: Create, retrieve, update multiple items
- Type-safe: Full Dart type safety
- Thread-Safe Locking (New in 1.1.0): File-based locking with stale lock detection
ClientWorkDbLockfor concurrent access protection- Automatic lock expiration (5000ms timeout)
- Stale lock cleanup with configurable timeout
- Lock information tracking (timestamp, user)
- Flexible Architecture: Choose
ClientWorkDb(lightweight) orClientWorkDbLock(thread-safe) - Factory Pattern: Polymorphic factory with dedicated input types for each implementation
- Minimal dependencies: Only
pathfor IO operations - Well tested: 224 tests across all implementations + 22 comprehensive locking tests
Installation #
dependencies:
work_db: ^1.1.0
Quick Start #
import 'package:work_db/work_db.dart';
void main() async {
// Create a factory
final factory = WorkDbFactory();
// Create a database instance for desktop/server
final db = factory.create(IoWorkDbFactoryInput(dataPath: './data'));
// Create an item
await db.create(ItemWithId(
id: 'user-1',
collection: 'users',
item: {'name': 'John Doe', 'email': 'john@example.com'},
));
// Retrieve the item
final user = await db.retrieve(ItemId(id: 'user-1', collection: 'users'));
print(user?.item['name']); // John Doe
// Update the item
await db.update(ItemWithId(
id: 'user-1',
collection: 'users',
item: {'name': 'John Doe', 'email': 'john.updated@example.com'},
));
// Delete the item
await db.delete(ItemId(id: 'user-1', collection: 'users'));
}
Thread-Safe Operations with Locking (New in 1.1.0) #
For concurrent access across multiple clients or isolates, use ClientWorkDbLock for automatic lock management:
import 'package:work_db/work_db.dart';
void main() async {
// Create a locked database instance for thread-safe operations
final db = ClientWorkDbLock(IoWorkDb('./data'));
// All operations are protected by file locks
await db.create(ItemWithId(
id: 'user-1',
collection: 'users',
item: {'name': 'John Doe'},
));
// Locks are automatically acquired and released
await db.update(ItemWithId(
id: 'user-1',
collection: 'users',
item: {'name': 'John Smith'},
));
}
Locking Features #
- Automatic Lock Management: Locks are acquired at operation start and released after completion
- Stale Lock Cleanup: Expired locks are automatically detected and cleaned up
- Lock Expiration: Locks expire after 5000ms if not explicitly released
- Configurable Timeout: Use
ClientWorkDbLock.withWaitingMs()for stale lock detection with custom timeout
// Enable stale lock detection with 300ms timeout
final db = ClientWorkDbLock.withWaitingMs(
IoWorkDb('./data'),
waitingMs: 300,
);
Choosing Between Implementations #
| Use Case | Implementation | Benefits |
|---|---|---|
| Single-threaded app | ClientWorkDb |
Minimal overhead, simpler code |
| Concurrent access | ClientWorkDbLock |
Thread-safe, automatic lock management |
| Testing | In-memory backend with ClientWorkDb or ClientWorkDbLock |
No file I/O, isolated state |
Platform-Specific Setup #
Desktop (Windows, macOS, Linux) & Server #
import 'package:work_db/work_db.dart';
final factory = WorkDbFactory();
final db = factory.create(IoWorkDbFactoryInput(dataPath: './my_app_data'));
Web #
import 'package:work_db/work_db.dart';
final factory = WorkDbFactory();
final db = factory.create(WebWorkDbFactoryInput());
Flutter Mobile (iOS, Android) #
import 'package:work_db/work_db.dart';
import 'package:path_provider/path_provider.dart';
Future<IWorkDb> createDatabase() async {
final dir = await getApplicationDocumentsDirectory();
final factory = WorkDbFactory();
return factory.create(IoWorkDbFactoryInput(dataPath: dir.path));
}
Testing #
import 'package:work_db/work_db.dart';
final factory = WorkDbFactory();
final db = factory.create(MemoryWorkDbFactoryInput());
API Reference #
Factory Methods #
| Method | Description |
|---|---|
WorkDbFactory().create(IoWorkDbFactoryInput) |
File system storage (Desktop/Server) |
WorkDbFactory().create(WebWorkDbFactoryInput) |
localStorage storage (Web) |
WorkDbFactory().create(MemoryWorkDbFactoryInput) |
In-memory storage (Testing) |
Each call to create() returns a new independent instance. You can create multiple databases with different paths:
final db1 = factory.create(IoWorkDbFactoryInput(dataPath: './data1'));
final db2 = factory.create(IoWorkDbFactoryInput(dataPath: './data2'));
// db1 and db2 are completely independent
Database Operations #
| Method | Description |
|---|---|
create(ItemWithId) |
Create a new item (throws if exists) |
createMultiple(List<ItemWithId>) |
Create multiple items |
update(ItemWithId) |
Update existing item (throws if not exists) |
createOrUpdate(ItemWithId) |
Create or update (upsert) |
createOrUpdateMultiple(List<ItemWithId>) |
Batch upsert |
retrieve(ItemId) |
Get item or null |
retrieveMultiple(List<ItemId>) |
Get multiple items |
delete(ItemId) |
Delete item (throws if not exists) |
deleteCollection(String) |
Delete entire collection |
clearDatabase() |
Delete all data |
getItemsInCollection(String) |
List item IDs in collection |
getCollections() |
List all collection names |
Examples #
Batch Operations #
// Create multiple items at once
await db.createMultiple([
ItemWithId(id: 'user-1', collection: 'users', item: {'name': 'Alice'}),
ItemWithId(id: 'user-2', collection: 'users', item: {'name': 'Bob'}),
ItemWithId(id: 'user-3', collection: 'users', item: {'name': 'Charlie'}),
]);
// Retrieve multiple items
final users = await db.retrieveMultiple([
ItemId(id: 'user-1', collection: 'users'),
ItemId(id: 'user-2', collection: 'users'),
]);
Upsert (Create or Update) #
// Creates if not exists, updates if exists
await db.createOrUpdate(ItemWithId(
id: 'settings',
collection: 'config',
item: {'theme': 'dark', 'language': 'en'},
));
Collection Management #
// List all collections
final collections = await db.getCollections();
print(collections); // ['users', 'config', 'posts']
// List items in a collection
final userIds = await db.getItemsInCollection('users');
print(userIds); // ['user-1', 'user-2', 'user-3']
// Delete entire collection
await db.deleteCollection('cache');
// Clear everything
await db.clearDatabase();
Nested Data #
await db.create(ItemWithId(
id: 'post-1',
collection: 'posts',
item: {
'title': 'Hello World',
'content': 'This is my first post',
'author': {
'name': 'John',
'email': 'john@example.com',
},
'tags': ['dart', 'flutter', 'database'],
'metadata': {
'views': 0,
'likes': 0,
'published': true,
},
},
));
Storage Format #
Data is stored as JSON files in a simple directory structure:
<dataPath>/
└── WorkDB/
├── users/
│ ├── user-1 (JSON file)
│ └── user-2 (JSON file)
└── posts/
└── post-1 (JSON file)
Error Handling #
import 'package:work_db/work_db.dart';
try {
await db.create(ItemWithId(
id: 'duplicate',
collection: 'test',
item: {'data': 'value'},
));
// This will throw ItemAlreadyExistsException
await db.create(ItemWithId(
id: 'duplicate',
collection: 'test',
item: {'data': 'new value'},
));
} on ItemAlreadyExistsException catch (e) {
print('Item ${e.id} already exists in ${e.collection}');
}
// Use createOrUpdate to avoid exceptions
await db.createOrUpdate(ItemWithId(
id: 'safe',
collection: 'test',
item: {'data': 'value'},
));
Testing Your Code #
import 'package:test/test.dart';
import 'package:work_db/work_db.dart';
void main() {
late IWorkDb db;
late WorkDbFactory factory;
setUp(() {
factory = WorkDbFactory();
db = factory.createNew(MemoryWorkDbFactoryInput());
});
test('should store and retrieve data', () async {
await db.create(ItemWithId(
id: 'test-1',
collection: 'test',
item: {'value': 42},
));
final result = await db.retrieve(ItemId(id: 'test-1', collection: 'test'));
expect(result?.item['value'], equals(42));
});
}
License #
LGPL v3 License - see LICENSE for details.
Contributing #
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.