flowdb 1.0.0 copy "flowdb: ^1.0.0" to clipboard
flowdb: ^1.0.0 copied to clipboard

A lightweight file-based local database for Dart and Flutter with collections, key-value stores, blob storage, queries, backups, and reactive FlowState.

flowdb #

A lightweight, file-based local database for Dart and Flutter. flowdb stores data on disk as JSON, with collections for structured records, key-value stores, chunked blob storage, backups, and optional encryption. It also includes reactive state (FlowState) and a Flutter widget (FlowBuilder) for stream-driven UIs.

Features #

  • File-based persistence — no native drivers or SQL; data lives under a database directory on the filesystem
  • Collections — document-style records with auto-generated CUID ids, CRUD, and rich querying
  • Key-value stores — simple get / set / remove storage scoped to the database
  • Blob storage — large binary files stored in configurable chunks with metadata in a collection
  • Query builder — filter, sort, paginate, project fields, and run bulk updates/deletes
  • Relations — define one-to-one, one-to-many, and many-to-many links between collections
  • Backups — snapshot the entire database directory
  • Optional encryption — encrypt collection and store payloads at rest
  • Sync and async APIs — most operations offer both foo() and fooSync() variants
  • Reactive stateFlowState<T> built on RxDart BehaviorSubject
  • Flutter integrationFlowBuilder connects FlowState streams to widgets

Requirements #

  • Dart SDK ^3.10.7
  • Flutter >=1.17.0 (only if you use package:flowdb/flutter.dart)

Installation #

Add flowdb to your pubspec.yaml:

dependencies:
  flowdb: ^1.0.0

For Dart-only projects, import the core library:

import 'package:flowdb/core.dart';

For Flutter apps that need FlowBuilder, import:

import 'package:flowdb/flutter.dart';

Quick start #

import 'package:flowdb/core.dart';

Future<void> main() async {
  final db = openDatabase('my_app', path: './data/my_app');

  final users = db.collection('users');

  final alice = await users.add({'name': 'Alice', 'age': 30});
  print(alice.id); // auto-generated CUID

  final found = await users.where('name', eq: 'Alice').getFirst();
  print(found?.data);

  final settings = db.store('settings');
  settings.set('theme', 'dark');
  print(settings.get('theme'));
}

Core concepts #

Database #

Open a database with openDatabase. All collections, stores, blobs, and backups live under the database path.

final db = openDatabase(
  'my_app',
  path: './data/my_app',   // optional; defaults to the database name
  encrypted: false,        // encrypt collection/store file contents
  isWeb: false,            // web platform hint
);
Method / property Description
collection(name) Get or create a collection
store(name) Get or create a key-value store
blobStorage(name) Get or create chunked blob storage
relation(...) Define a relation between collections
backup(name) Create a filesystem backup
reset() / clear() Delete and recreate the database directory
drop() Delete the database directory entirely
size / sizeFormatted Total on-disk size

Collections and records #

A collection holds records — maps of fields keyed by string, each with a stable id.

final posts = db.collection('posts');

// Create
final record = await posts.add({'title': 'Hello', 'published': true});
final batch = await posts.addMany([
  {'title': 'First'},
  {'title': 'Second'},
]);

// Read by id
final one = await posts.getById(record.id);

// Read all
final all = await posts.getAll();
final total = await posts.count;

Records support nested field access via dot notation:

record.set('author.name', 'Alice');
final authorName = record.get('author.name');

Export a collection as CSV:

final csv = await posts.exportAsCSV();

Querying #

Chain filters on collection.where(...), collection.builder, or the query returned by where:

final results = await posts
    .where('published', eq: true)
    .and('views', gte: 100)
    .orderBy('title')
    .skip(10)
    .limit(20)
    .get();

final first = await posts.where('title', startsWith: 'Hello').getFirst();
final matching = await posts.where('tags', contains: 'dart').get();

Comparison operators

Parameter Meaning
eq / neq Equal / not equal
gt / gte / lt / lte Numeric ordering
contains / doesNotContain Substring or list membership
startsWith / endsWith String prefix / suffix
regexMatch Regular expression
between, betweenStartInclusive, betweenEndInclusive, betweenBothInclusive Range checks
isNull / isNotNull Null checks
isEmpty / isNotEmpty Empty string or collection
filter Custom bool Function(dynamic value)

Other query methods

Method Description
or(...) OR condition (after an initial where)
pick / pickMany Include only selected fields
omit / omitMany Exclude fields from results
orderBy(field, desc: true) Sort results
skip / limit / take Pagination
include / includeMany Load related data (when relations are configured)
getShuffled / getRandom / getRandomMany Random access
update / updateMany Update matching records
delete / deleteMany Delete matching records
upsert Update if matched, otherwise insert
addIfAbsent Insert only when no match

Sync variants mirror the async API: getSync(), addSync(), countSync, and so on.

Key-value stores #

Stores hold arbitrary JSON-serializable values under string keys.

final cache = db.store('cache');

cache.set('lastSync', DateTime.now().toIso8601String());
cache.setFrom('counter', (v) => (v as int? ?? 0) + 1, 0);

if (cache.has('lastSync')) {
  print(cache.get('lastSync'));
}

final all = cache.all(); // Map of all entries
cache.clear();
cache.drop(); // delete the store from disk

Blob storage #

Store large binary data in chunked files. Metadata is kept in an internal collection; bytes are written in configurable chunk sizes (default 512 KB).

import 'dart:typed_data';

final files = db.blobStorage('files', chunksSize: 1024 * 512);

await files.add(
  metadata: {'filename': 'photo.png', 'mime': 'image/png'},
  bytes: Uint8List.fromList([/* ... */]),
);

final blob = await files.get((meta) => meta.where('filename', eq: 'photo.png'));
final bytes = await blob?.bytes;

Relations #

Define how collections link to each other. Relations are registered on the database and can be used with query include.

db
    .relation('users', 'id', name: 'posts')
    .hasMany('posts', 'authorId');

// one-to-one
db.relation('users', 'profileId', name: 'profile').hasOne('profiles', 'id');

// many-to-many
db.relation('users', 'id', name: 'tags').manyToMany('tags', 'id');

Backups #

Backups copy the database directory (excluding existing backup folders) into backups/<name>/.

final snapshot = db.backup('before-migration');
print(db.backups); // list of Backup instances

db.removeBackup('before-migration');
db.removeAllBackups();

Reactive state (FlowState) #

FlowState<T> wraps a seeded BehaviorSubject for synchronous reads and stream updates.

final counter = FlowState<int>(0);

counter.listen((value) => print('count: $value'));
counter.update(1);
counter.updateFrom((v) => v + 1);

final combined = FlowState.combine2(
  counter,
  FlowState<String>(''),
  (count, label) => '$label: $count',
);

await counter.close();

FlowState.combine2 through combine9, plus FlowState.list, merge multiple states into one.

Flutter integration #

Use FlowBuilder to rebuild widgets when a FlowState changes:

import 'package:flowdb/flutter.dart';
import 'package:flutter/material.dart';

class CounterView extends StatelessWidget {
  const CounterView({super.key, required this.counter});

  final FlowState<int> counter;

  @override
  Widget build(BuildContext context) {
    return FlowBuilder<int>(
      flow: counter,
      builder: (value) => Text('Count: $value'),
      onLoadingBuilder: () => const CircularProgressIndicator(),
      onErrorBuilder: (e) => Text('Error: $e'),
      onNoDataBuilder: () => const Text('No data'),
    );
  }
}

Utilities #

The core library also exports helpers used internally and available for app code:

Export Purpose
BGWorker Run a function in a separate isolate
Json JSON encode/decode, nested get/update, CSV conversion
Encryptor Encrypt/decrypt strings for at-rest storage
Format Human-readable byte sizes
Performance Timing utilities
PrintColored ANSI-colored console output
Random ID generation (CUID)

On-disk layout #

A database directory typically looks like:

my_app/
├── collections/
│   └── users          # newline-delimited JSON records
├── stores/
│   └── settings       # JSON key-value file
├── blobs/
│   └── files/
│       ├── _metadata/ # blob metadata collection
│       └── chunks/    # binary chunks
└── backups/
    └── snapshot_name/

Sync vs async #

Most collection and query methods exist in pairs:

  • add / addSync
  • getAll / getAllSync
  • where(...).get() / where(...).getSync()

Use async methods in UI code and isolates where blocking I/O is undesirable. Use sync methods in scripts, tests, or startup paths where simplicity matters.

Encryption #

Pass encrypted: true when opening a database to encrypt collection and store file contents. Encrypted databases propagate the setting to collections and stores created through db.collection() and db.store().

Additional information #

2
likes
130
points
34
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight file-based local database for Dart and Flutter with collections, key-value stores, blob storage, queries, backups, and reactive FlowState.

License

MIT (license)

Dependencies

crypto, flutter, path, rxdart

More

Packages that depend on flowdb