work_db 1.3.0 copy "work_db: ^1.3.0" to clipboard
work_db: ^1.3.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 #

pub package License: LGPL v3

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
    • ClientWorkDbLock for concurrent access protection
    • Automatic lock expiration (5000ms timeout)
    • Stale lock cleanup with configurable timeout
    • Lock information tracking (timestamp, user)
  • Flexible Architecture: Choose ClientWorkDb (lightweight) or ClientWorkDbLock (thread-safe)
  • Factory Pattern: Polymorphic factory with dedicated input types for each implementation
  • Minimal dependencies: Only path for IO operations
  • Synchronous API (New in 1.2.0): Full *Sync counterparts for all operations via IWorkDbSync
    • createSync, updateSync, retrieveSync, deleteSync, and all other operations available synchronously
    • ClientWorkDbLockSync for thread-safe synchronous operations
  • Record Limit per Collection (New in 1.3.0): Optional maxRecordsPerCollection with automatic eviction of oldest records
  • Well tested: 397 tests across all implementations + locking tests + full sync test coverage

Installation #

dependencies:
  work_db: ^1.3.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
Sync single-threaded ClientWorkDb (sync API) No async overhead, blocking calls
Sync concurrent access ClientWorkDbLockSync Thread-safe sync operations
Testing In-memory backend with ClientWorkDb or ClientWorkDbLock No file I/O, isolated state

Synchronous API (New in 1.2.0) #

All database operations are available as synchronous methods. ClientWorkDb implements IWorkDbSync directly — no wrapper needed:

import 'package:work_db/work_db.dart';

void main() {
  final db = ClientWorkDb(IoWorkDb('./data'));

  // Synchronous create
  db.createSync(ItemWithId(
    id: 'user-1',
    collection: 'users',
    item: {'name': 'John Doe'},
  ));

  // Synchronous retrieve
  final user = db.retrieveSync(ItemId(id: 'user-1', collection: 'users'));
  print(user?.item['name']); // John Doe

  // Synchronous update
  db.updateSync(ItemWithId(
    id: 'user-1',
    collection: 'users',
    item: {'name': 'John Smith'},
  ));

  // Synchronous delete
  db.deleteSync(ItemId(id: 'user-1', collection: 'users'));
}

Thread-Safe Synchronous Operations #

For concurrent synchronous access, use ClientWorkDbLockSync:

import 'package:work_db/work_db.dart';

void main() {
  final db = ClientWorkDbLockSync(IoWorkDb('./data'));

  // Sync operations are protected by file locks
  db.createSync(ItemWithId(
    id: 'doc-1',
    collection: 'documents',
    item: {'title': 'Hello'},
  ));

  // With stale lock detection
  final dbWithTimeout = ClientWorkDbLockSync.withWaitingMs(
    IoWorkDb('./data'),
    waitingMs: 300,
  );
}

ClientWorkDbLockSync also inherits all async operations from ClientWorkDbLock, so you can mix sync and async calls on the same instance.

Record Limit per Collection (New in 1.3.0) #

Set an optional maximum number of records per collection. When the limit is exceeded, the oldest records are automatically evicted:

// Keep max 10 log entries per collection
final db = ClientWorkDb(
  IoWorkDb('./data'),
  maxRecordsPerCollection: 10,
);

Works with all backends and factory methods:

// Via factory
final db = factory.create(MemoryWorkDbFactoryInput(
  maxRecordsPerCollection: 100,
));

// Via convenience class
final db = WorkDb.memory(maxRecordsPerCollection: 3);

// With locking
final db = ClientWorkDbLock(
  IoWorkDb('./data'),
  maxRecordsPerCollection: 50,
);

Eviction is triggered on create, createMultiple, createOrUpdate, and createOrUpdateMultiple (and their sync variants). Items are sorted by creation timestamp and the oldest are removed when the limit is exceeded.

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 #

All operations are available in both async (Future<T>) and sync (T) variants.

Async Method Sync Method Description
create(ItemWithId) createSync(ItemWithId) Create a new item (throws if exists)
createMultiple(List<ItemWithId>) createMultipleSync(List<ItemWithId>) Create multiple items
update(ItemWithId) updateSync(ItemWithId) Update existing item (throws if not exists)
createOrUpdate(ItemWithId) createOrUpdateSync(ItemWithId) Create or update (upsert)
createOrUpdateMultiple(List<ItemWithId>) createOrUpdateMultipleSync(List<ItemWithId>) Batch upsert
retrieve(ItemId) retrieveSync(ItemId) Get item or null
retrieveMultiple(List<ItemId>) retrieveMultipleSync(List<ItemId>) Get multiple items
delete(ItemId) deleteSync(ItemId) Delete item (throws if not exists)
deleteCollection(String) deleteCollectionSync(String) Delete entire collection
clearDatabase() clearDatabaseSync() Delete all data
getItemsInCollection(String) getItemsInCollectionSync(String) List item IDs in collection
getCollections() getCollectionsSync() 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.

0
likes
130
points
140
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight, cross-platform local database for Dart and Flutter. Simple key-value storage with collections, supporting Desktop, Web, and Mobile.

Repository (GitHub)
View/report issues

Topics

#database #storage #local-storage #persistence #key-value

License

unknown (license)

Dependencies

path

More

Packages that depend on work_db