cindel 0.5.11
cindel: ^0.5.11 copied to clipboard
Ultra-fast, lightweight NoSQL local database for Flutter and Dart apps, powered by a compact Rust native core.
Cindel Database #
Extremely fast, easy to use and fully async NoSQL local database for Flutter and Dart apps, powered by a generated Dart API and a compact Rust native core.
Quickstart | Features | CRUD | Queries | Watchers | Embedded Objects | Native Binaries
Cindel is a Flutter-first local database with typed collections, generated schemas, MDBX storage by default, and SQLite available only when requested.
Status #
Cindel is in active pre-1.0 development. The API and storage format can still change before a stable release. New projects should treat old preview database files as disposable while the optimized native format settles.
Features #
- Typed collections generated from regular Dart model classes.
- Freezed classic class and primary factory models for immutable entities with
generated
copyWith, equality, and hashCode. - Rust native core behind Dart FFI.
- MDBX default backend with SQLite as an explicit secondary backend.
- Native auto-increment ids.
- Bulk writes, reads, updates, and deletes.
- Indexed equality, range, prefix, unique, hash, case-insensitive, word-token, composite, and primitive-list queries.
- Filter builders, sorting, pagination, distinct, property projections, and property aggregates.
- Explicit read and write transactions.
- Document, collection, object, query, and lazy watchers.
- Embedded objects and embedded object lists.
- In-memory databases for tests.
- Schema metadata and compatible additive schema version bumps.
Quickstart #
1. Add dependencies #
For Flutter apps, add Cindel plus the native library package:
dependencies:
cindel: ^0.5.11
cindel_flutter_libs: ^0.5.11
dev_dependencies:
build_runner: ^2.15.0
cindel_generator: ^0.5.9
Pure Dart projects can depend on cindel directly and provide a native library
path with CINDEL_NATIVE_LIBRARY when needed.
2. Create a collection #
import 'package:cindel/cindel.dart';
part 'user.g.dart';
@Collection(name: 'users')
class User {
Id dbId = autoIncrement;
@Index(unique: true)
late String email;
@index
late String name;
bool active = true;
DateTime createdAt = DateTime.now().toUtc();
}
Freezed classic classes
Cindel can also generate schemas for Freezed classic classes. This lets you keep
immutable models while Freezed provides copyWith, equality, and hashCode:
Freezed models also need freezed_annotation as an app dependency and
freezed as a development dependency.
import 'package:cindel/cindel.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
@Collection(name: 'users')
class User with _$User {
const User({
required this.dbId,
required this.email,
required this.name,
this.active = true,
});
@override
final Id dbId;
@override
@Index(unique: true)
final String email;
@override
final String name;
@override
final bool active;
}
Cindel also supports the common Freezed primary factory style:
@freezed
@Collection(name: 'users')
abstract class User with _$User {
const factory User({
required Id dbId,
required String email,
required String name,
@Default(true) bool active,
}) = _User;
}
IMPORTANT: Freezed union/sealed multi-constructor models are not supported.
3. Generate code #
dart run build_runner build --delete-conflicting-outputs
4. Open a database #
final db = await Cindel.open(
directory: directory.path,
schemas: [UserSchema],
);
MDBX is the default backend. SQLite is available only when requested explicitly:
final db = await Cindel.open(
directory: directory.path,
schemas: [UserSchema],
backend: CindelStorageBackend.sqlite,
);
For tests and short-lived work:
final db = await Cindel.openInMemory(schemas: [UserSchema]);
CRUD #
Generated collections are available directly from the database handle:
final jhon = User()
..name = 'Jhon Doe'
..email = 'jhon@example.com'
..active = true;
await db.users.put(jhon);
final saved = await db.users.get(jhon.dbId);
await db.users.delete(jhon.dbId);
Bulk operations use native batch paths:
final maria = User()
..name = 'Maria Cruz'
..email = 'maria@example.com';
final taylor = User()
..name = 'Taylor Reed'
..email = 'taylor@example.com';
await db.users.putMany([jhon, maria, taylor]);
final users = await db.users.getAll([jhon.dbId, maria.dbId, 404]);
await db.users.deleteAll([jhon.dbId, maria.dbId]);
Use transactions when multiple operations must commit together:
await db.writeTxn(() async {
await db.users.put(jhon);
await db.users.put(maria);
});
final activeUsers = await db.readTxn(() {
return db.users.filter().activeEqualTo(true).findAll();
});
Queries #
Generated query builders start from indexed where clauses or collection
filters:
final jhon = await db.users
.where()
.emailEqualTo('jhon@example.com')
.findFirst();
final activeUsers = await db.users
.filter()
.activeEqualTo(true)
.sortByName()
.findAll();
String indexes get prefix helpers:
final people = await db.users.where().nameStartsWith('Jh').findAll();
Queries support counts, deletes, pagination, distinct values, property projections, and aggregates:
final count = await db.users.filter().activeEqualTo(true).count();
final names = await db.users
.filter()
.activeEqualTo(true)
.sortByName()
.distinctByEmail()
.nameProperty()
.findAll();
final maxId = await db.users.all().dbIdProperty().max();
The lower-level manual document API remains available:
await db.put('users', 1, {
'name': 'Jhon Doe',
'email': 'jhon@example.com',
'active': true,
});
final document = await db.get('users', 1);
Watchers #
Cindel watchers expose Dart streams for objects, collections, queries, and manual documents:
final sub = db.users
.filter()
.activeEqualTo(true)
.sortByName()
.watch()
.listen((users) {
// Rebuild visible state.
});
Lazy watchers emit only an invalidation signal:
final sub = db.users.watchCollectionLazy().listen((_) {
// Refresh cached state.
});
Embedded Objects #
Use @Embedded or @embedded for value objects stored inside a parent
collection document. Embedded objects are not root collections; they are
serialized as part of the parent object.
@Collection(name: 'emails')
class Email {
Id dbId = autoIncrement;
String? subject;
Recipient? sender;
List<Recipient>? recipients;
}
@Embedded()
class Recipient {
String? name;
String? address;
RecipientMetadata? metadata;
}
@embedded
class RecipientMetadata {
String? label;
}
Generated filters can query fields inside a single embedded object, including nested embedded objects:
final messages = await db.emails
.filter()
.sender((recipient) => recipient.addressEqualTo('ada@example.com'))
.findAll();
final leadMessages = await db.emails
.filter()
.sender((recipient) {
return recipient.metadata((metadata) {
return metadata.labelEqualTo('lead');
});
})
.findAll();
Embedded object fields and embedded object lists can be stored through the native typed document path. The generated writer uses native object/list hooks, and the generated reader hydrates embedded objects without requiring a manual JSON round-trip.
Generated helpers support equality for the whole embedded value and element equality for embedded lists. Nested field filter helpers are generated for single embedded object fields. Nested query helpers for fields inside embedded object lists are not generated.
Embedded indexes are not supported. Put @Index on root collection fields, not
inside @Embedded classes.
Supported Platforms #
The current package line ships prebuilt Flutter libraries for Android, Windows,
and Linux through cindel_flutter_libs.
iOS and macOS are planned but are not advertised as supported until their native binaries are generated and validated.
Native Binaries #
Flutter apps should depend on cindel_flutter_libs so the native runtime is
bundled automatically.
When testing a custom local build, set CINDEL_NATIVE_LIBRARY to an absolute
path before running Dart tests or tools:
$env:CINDEL_NATIVE_LIBRARY = 'D:\path\to\cindel_native.dll'
Unit Tests #
Use in-memory databases for fast tests:
final db = await Cindel.openInMemory(schemas: [UserSchema]);
addTearDown(db.close);
License #
Cindel is licensed under the Apache License, Version 2.0. See the repository license file for details.