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.

Cindel uses Dart annotations to mark model classes and fields for code generation. The cindel_generator reads these annotations at build time and produces typed collection getters, serializers, query helpers, and schema metadata. All annotations live in package:cindel_annotations; when you depend on package:cindel, they are re-exported and available through a single import.

@Collection / @collection

Marks a Dart class as a root persisted collection. Every class that should have its own collection in the database must be annotated with @Collection or the shorthand @collection. Class: Collection
name
String?
default:"null"
The stored collection name. When omitted, the generator derives the name from the Dart class name by lowercasing the first letter (e.g. User"user").
indexes
List<CompositeIndex>
default:"[]"
Collection-level composite indexes. Use this for indexes that span more than one field. Single-field indexes are declared directly on the field with @Index.
Shorthand constant: @collection — equivalent to @Collection() with no name override and no composite indexes.

When to use

Use @Collection on every root model class. Prefer the named constructor when you need a specific storage name or composite indexes; use the @collection shorthand for simple classes where the generated default name is fine.
// Explicit name and a composite index
@Collection(
  name: 'events',
  indexes: [
    CompositeIndex(['accountId', 'createdAt']),
  ],
)
class Event {
  Id dbId = autoIncrement;

  @Index()
  late int accountId;

  @Index()
  late DateTime createdAt;
}

// Shorthand — stored as "article"
@collection
class Article {
  Id dbId = autoIncrement;
  late String title;
}

@Index / @index

Marks a single field as indexed. The generator creates a native index for the field and adds typed where() helpers that use it. Class: Index
unique
bool
default:"false"
When true, the index enforces that no two documents have the same value for this field.
replace
bool
default:"false"
When true and unique is also true, a write that conflicts with an existing document replaces the conflicting document instead of throwing. The generator produces a putByFieldName helper on the collection.
caseSensitive
bool
default:"true"
Controls whether string comparisons are case-sensitive. Only applies to String fields.
type
CindelIndexType
default:"CindelIndexType.value"
The storage strategy for this index. See CindelIndexType below.
Shorthand constant: @index — equivalent to @Index() with all defaults.

When to use

Add @Index to any field you want to query through generated where() helpers or that would benefit from a backend index. For unique natural keys (e.g. email, sku), set unique: true. Combine with replace: true to enable natural-key upserts.
@collection
class Account {
  Id dbId = autoIncrement;

  @Index(unique: true, replace: true)
  late String username;

  @Index(caseSensitive: false)
  late String displayName;

  @Index(type: CindelIndexType.words, caseSensitive: false)
  late String bio;

  @Index(type: CindelIndexType.multiEntry, caseSensitive: false)
  List<String> tags = const [];
}

CindelIndexType

Controls how the native backend stores an index entry.
CaseDescription
valueStores the original sortable value. Supports equality and range helpers (e.g. greaterThan, between). Default.
hashStores a compact stable hash. Supports equality helpers only; does not support range queries.
wordsSplits a string into word tokens. Supports exact token lookup and token-prefix lookup.
multiEntryAdds one index entry per item in a primitive list. Supports list-membership queries.
@collection
class Article {
  Id dbId = autoIncrement;

  @Index()                                                // value — sortable
  late DateTime publishedAt;

  @Index(type: CindelIndexType.hash)                     // hash — equality only
  late String externalId;

  @Index(type: CindelIndexType.words, caseSensitive: false) // word tokens
  late String body;

  @Index(type: CindelIndexType.multiEntry)               // list membership
  List<String> tagIds = const [];
}

@Embedded / @embedded

Marks a Dart class as an embedded value object. Embedded objects are stored inside the parent collection document rather than in their own collection. They do not have an Id field and cannot be queried independently. Class: Embedded This annotation takes no parameters. Shorthand constant: @embedded — equivalent to @Embedded().

When to use

Use @embedded for value types that only make sense as part of a parent document — addresses, metadata blocks, nested lists of structured values, etc. Embedded objects can be nested, and the generator produces typed filter helpers that descend into them.
@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;
}

Declares a read-only inverse link. When a collection holds a CindelLink<T> or CindelLinks<T> forward link, the target class can expose the inverse through a CindelLinks field annotated with @Backlink. Class: Backlink
to
String
required
The Dart field name of the forward link on the linked collection that this backlink mirrors.

When to use

Use @Backlink when you want to navigate a relation in reverse without duplicating any storage. The backlink is populated by loading the forward relation — it is read-only and cannot be saved directly.
@Collection(name: 'artists')
class Artist {
  Id dbId = autoIncrement;
  late String name;

  @Backlink(to: 'featuredArtists')
  final songs = CindelLinks<Song>();
}

@Collection(name: 'songs')
class Song {
  Id dbId = autoIncrement;
  late String title;

  final featuredArtists = CindelLinks<Artist>();
  final primaryArtist = CindelLink<Artist>();
}

@Name

Overrides the stored name for a collection, field, or embedded field. The generator uses the provided string everywhere the name appears in schemas, indexes, and native queries. Class: Name
value
String
required
The storage name to use instead of the Dart name.

When to use

Use @Name when the Dart name and the desired database name differ — for example, when migrating a renamed field while preserving backward compatibility with existing stored documents, or when following a naming convention that differs from Dart style.
@Name('products')
@collection
class Product {
  Id dbId = autoIncrement;

  @Name('sku_code')
  @Index(unique: true)
  late String sku;
}

@Enumerated

Configures how an enum field is persisted. Without this annotation, the generator falls back to the default persistence strategy for the detected enum type. Class: Enumerated
type
CindelEnumType
required
The persistence strategy. See CindelEnumType below.
valueField
String?
default:"null"
The name of the enum instance field whose value should be stored when type is CindelEnumType.value.

When to use

Use @Enumerated whenever the default persistence strategy is not appropriate. Prefer CindelEnumType.name for human-readable storage, CindelEnumType.ordinal when compact integer storage is important, and CindelEnumType.value when the enum carries a stable string or integer code field that should survive reordering.
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;
}

CindelEnumType

Controls how an enum value is written to storage.
CaseStored asNotes
nameThe enum case name string (e.g. "pro")Human-readable; breaks if cases are renamed.
ordinalThe zero-based case index integerCompact; breaks if case order changes.
valueThe value of a named enum instance fieldStable across renames and reorders when the field value is kept constant. Requires valueField on @Enumerated.

@Ignore / @ignore

Excludes a field from Cindel persistence. The generator skips the annotated field entirely — it is not stored, not indexed, and not part of any schema. Class: Ignore This annotation takes no parameters. Shorthand constant: @ignore — equivalent to @Ignore().

When to use

Use @ignore for computed properties, UI state, or any runtime-only field that should not be written to the database.
@collection
class User {
  Id dbId = autoIncrement;
  late String firstName;
  late String lastName;

  @ignore
  String get fullName => '$firstName $lastName';

  @ignore
  bool isSelected = false;
}

CompositeIndex

Declares a multi-field index at the collection level. Pass one or more CompositeIndex instances in the indexes parameter of @Collection. Class: CompositeIndex
fields
List<String>
required
The field names included in the composite key, in index order. Field names must match the Dart field names (or the @Name override if one is present).
unique
bool
default:"false"
Whether the combined composite value must be unique across documents.
replace
bool
default:"false"
Whether a write that conflicts with this unique composite index replaces the conflicting document instead of throwing.
caseSensitive
bool
default:"true"
Whether string comparisons within the composite key are case-sensitive.

When to use

Use CompositeIndex when a query filters or sorts on a combination of fields together and a single-field index would not be selective enough, or when you need a multi-field uniqueness constraint.
@Collection(
  name: 'events',
  indexes: [
    CompositeIndex(['accountId', 'createdAt']),
    CompositeIndex(['accountId', 'type'], unique: true),
  ],
)
class Event {
  Id dbId = autoIncrement;

  @Index()
  late int accountId;

  @Index()
  late DateTime createdAt;

  late String type;
}

Id and autoIncrement

Id is a Dart typedef for int. Declare one Id field per collection class — the generator treats it as the document identifier. autoIncrement is a sentinel const Id equal to -1. When a document’s id field holds autoIncrement at write time, Cindel allocates the next available id and assigns it back to the object.
// From cindel_annotations:
typedef Id = int;
const Id autoIncrement = -1;

Usage

@collection
class User {
  Id dbId = autoIncrement;   // generator writes the allocated id here after put()
  late String email;
}

// After put(), dbId holds the allocated integer id:
final user = User()..email = 'ada@example.com';
await db.users.put(user);
print(user.dbId); // e.g. 1
You can also assign an explicit id by setting dbId to any positive integer before calling put. Cindel will use that value directly rather than allocating a new one.

Build docs developers (and LLMs) love