Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mainser/cindel/llms.txt
Use this file to discover all available pages before exploring further.
Every Cindel database is built around annotated Dart classes. You write a plain class, add the right annotations, run build_runner, and the generator produces a typed schema (UserSchema), a binary serializer, a typed database extension getter (db.users), and fully-typed where() / filter() query helpers — all from the same class you already use in your application code. This page walks through every annotation Cindel supports and shows how they map to generated output.
Marking a Class as a Collection
Use @Collection to declare a root persisted object — one that gets its own collection in the database:
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();
}
@Collection takes two optional named parameters:
| Parameter | Type | Description |
|---|
name | String? | Stored collection name. Omit to derive it from the class name. |
indexes | List<CompositeIndex> | Composite indexes declared for this collection. |
The lowercase @collection constant is a shorthand for @Collection() when you don’t need a custom name or composite indexes:
@collection
class Account {
Id dbId = autoIncrement;
@Index(unique: true, replace: true)
late String username;
String? displayName;
}
The Required dbId Field
Every collection class must declare exactly one Id field. The conventional name is dbId, and the conventional default value is the autoIncrement sentinel:
Cindel uses this field to assign and read numeric document identifiers. When you call put(object) with dbId == autoIncrement, Cindel allocates the next available id from the native engine and writes it back to the field before persisting the object.
Overriding Stored Names with @Name
By default, Cindel stores the collection under the name you pass to @Collection, and stores each field under its Dart name. Use @Name to override the stored name for a collection or a field independently of the Dart identifier:
@Name('products')
@collection
class Product {
Id dbId = autoIncrement;
@Name('sku_code')
@Index(unique: true)
late String sku;
}
In this example the collection is stored as products and the field is stored as sku_code, even though the Dart field is named sku. @Name is useful when migrating from a previous schema where the stored name must remain stable.
Supported Field Types
Cindel persists the following Dart types:
| Category | Types |
|---|
| Scalars | bool, int, double, String |
| Temporal | DateTime, Duration |
| Enums | Any Dart enum (see @Enumerated below) |
| Nullable variants | bool?, int?, double?, String?, DateTime?, etc. |
| Embedded objects | Classes annotated with @embedded (see below) |
| Lists | List<T> where T is any of the above non-list types |
| Lists of embedded objects | List<EmbeddedClass> or List<EmbeddedClass?> |
Excluding Fields with @ignore
Fields that should not be persisted — computed properties, runtime caches, or UI state — can be excluded from the generated schema with @ignore:
@ignore
String runtimeOnlyLabel = '';
The generator skips @ignore fields entirely; they are never serialized, indexed, or included in queries.
Indexes
Add @Index to any field you want to query through generated where() helpers or that the backend should optimize:
@collection
class Article {
Id dbId = autoIncrement;
@Index()
late String title;
@Index(caseSensitive: false)
late String normalizedTitle;
@Index(type: CindelIndexType.words, caseSensitive: false)
late String body;
@Index(type: CindelIndexType.multiEntry, caseSensitive: false)
List<String> tags = const [];
}
@Index parameters:
| Parameter | Default | Description |
|---|
unique | false | Requires unique values across documents. |
replace | false | Automatically replaces the conflicting document on unique constraint violations. |
caseSensitive | true | Controls case sensitivity for string field lookups. |
type | CindelIndexType.value | Storage strategy: value, hash, words, or multiEntry. |
Declare composite indexes on the collection annotation itself:
@Collection(
name: 'events',
indexes: [
CompositeIndex(['accountId', 'createdAt']),
],
)
class Event {
Id dbId = autoIncrement;
@Index()
late int accountId;
@Index()
late DateTime createdAt;
}
Enums with @Enumerated
Cindel can persist enums by case name, by ordinal (index), or by the value of an instance field. Annotate the field with @Enumerated and pass one of the three CindelEnumType strategies:
enum Plan {
free('free'),
pro('pro');
const Plan(this.code);
final String code;
}
@collection
class Subscription {
Id dbId = autoIncrement;
@Enumerated(CindelEnumType.value, valueField: 'code')
Plan plan = Plan.free;
}
| Strategy | Description |
|---|
CindelEnumType.name | Stores the enum case name as a string (e.g. 'free'). |
CindelEnumType.ordinal | Stores the zero-based index of the enum case. |
CindelEnumType.value | Stores the value of the named valueField on the enum instance. |
Embedded Objects with @Embedded
Use @embedded for value objects that are stored inside a parent collection document rather than in their own collection. Embedded objects cannot have an Id field and are never queried or written independently:
@collection
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;
}
Embedded objects can be nested to arbitrary depth. Generated filter helpers let you query into them — see Queries for examples.
Add @Index annotations to fields on the root collection class, not inside embedded classes. Embedded classes cannot declare indexes.
Freezed Support
Cindel supports two Freezed class shapes:
Classic class
Single primary factory
import 'package:cindel/cindel.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
@Collection(name: 'users')
abstract class User with _$User {
User._();
factory User({
required Id dbId,
@Index(unique: true) required String email,
@Index() required String name,
@Default(true) bool active,
}) = _User;
}
import 'package:cindel/cindel.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product.freezed.dart';
part 'product.g.dart';
@freezed
@Collection(name: 'products')
abstract class Product with _$Product {
const factory Product({
required Id dbId,
@Index(unique: true) required String sku,
@Index() required String name,
@Default(true) bool active,
}) = _Product;
}
Freezed union / sealed models with multiple constructors are not supported by Cindel. Only classes with a single default constructor or a single primary const factory constructor can be used as Cindel collections.
Generated Output
After running dart run build_runner build --delete-conflicting-outputs, the generator produces a .g.dart file next to your model file. For the User class above, the output is user.g.dart and includes:
UserSchema — a CindelCollectionSchema<User> instance containing field metadata, serializers, and composite index descriptors.
db.users — a typed extension getter on CindelDatabase that returns a CindelTypedCollection<User>.
- Typed query helpers —
where() and filter() builders with generated methods for every indexed and persisted field.
The part directive at the top of your model file is required:
Pass the generated schema to Cindel.open so the database knows about the collection:
final db = await Cindel.open(
directory: directory.path,
schemas: [UserSchema],
);