Skip to main content

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:
ParameterTypeDescription
nameString?Stored collection name. Omit to derive it from the class name.
indexesList<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:
Id dbId = autoIncrement;
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:
CategoryTypes
Scalarsbool, int, double, String
TemporalDateTime, Duration
EnumsAny Dart enum (see @Enumerated below)
Nullable variantsbool?, int?, double?, String?, DateTime?, etc.
Embedded objectsClasses annotated with @embedded (see below)
ListsList<T> where T is any of the above non-list types
Lists of embedded objectsList<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:
ParameterDefaultDescription
uniquefalseRequires unique values across documents.
replacefalseAutomatically replaces the conflicting document on unique constraint violations.
caseSensitivetrueControls case sensitivity for string field lookups.
typeCindelIndexType.valueStorage 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;
}
StrategyDescription
CindelEnumType.nameStores the enum case name as a string (e.g. 'free').
CindelEnumType.ordinalStores the zero-based index of the enum case.
CindelEnumType.valueStores 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:
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;
}
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 helperswhere() and filter() builders with generated methods for every indexed and persisted field.
The part directive at the top of your model file is required:
part 'user.g.dart';
Pass the generated schema to Cindel.open so the database knows about the collection:
final db = await Cindel.open(
  directory: directory.path,
  schemas: [UserSchema],
);

Build docs developers (and LLMs) love