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.

CindelQuery<T> is the typed query builder that every generated collection helper builds on. You obtain a CindelQuery<T> by calling db.collection.all(), db.collection.where(), or db.collection.filter(), and then refine it by chaining modifiers — sort, distinct, offset, limit, and filter predicates — before executing a terminal operation. Query objects are immutable: every modifier method returns a new CindelQuery<T> with the requested change applied, leaving the original untouched. At execution time, Cindel selects the most efficient path available — native binary plans on MDBX, native SQL on the SQLite backends, or Dart-side evaluation as a fallback.

Terminal Read Operations

Terminal operations execute the query and return a result. They must be called last in any chain.

findAll

Future<List<T>> findAll()
Executes the query and returns every matching object as a typed list. The returned list is in insertion order unless the query carries sort keys, in which case results follow the specified sort. An empty collection or a query that matches no documents returns an empty list.
// All active users, sorted by name
final users = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .findAll();

// All users — no filter
final all = await db.users.all().findAll();

findFirst

Future<T?> findFirst()
Returns the first object matching the query, or null if no documents match. Equivalent to calling findAll() and taking the first element, but expressed more clearly in code.
final ada = await db.users
    .where()
    .emailEqualTo('ada@example.com')
    .findFirst();

if (ada == null) {
  print('User not found');
}

count

Future<int> count()
Returns the number of documents matching the query. On MDBX and SQLite native backends, count delegates to a native counting path and avoids deserializing the matching documents.
final activeCount = await db.users
    .filter()
    .activeEqualTo(true)
    .count();

print('Active users: $activeCount');

Terminal Mutation Operations

deleteAll

Future<int> deleteAll()
Deletes every document matching the query atomically and returns the number of documents deleted. If no documents match, returns 0.
final deleted = await db.users
    .filter()
    .activeEqualTo(false)
    .deleteAll();

print('Removed $deleted inactive users');

deleteFirst

Future<bool> deleteFirst()
Deletes the first object matching the query and returns true if a document was deleted, or false if no documents matched. Respects the current sort order to determine which document is “first”.
final removed = await db.users
    .filter()
    .activeEqualTo(false)
    .sortByCreatedAt()
    .deleteFirst();

print(removed ? 'Oldest inactive user removed' : 'No inactive users found');

updateAll

Future<int> updateAll(Map<String, Object?> changes)
Applies the field updates in changes to every document matching the query and returns the number of documents updated. The keys in changes are persisted field names as they appear in the generated schema. updateAll requires a native query plan — attempting to call it without one throws an UnsupportedError. The id field cannot be updated and raises an ArgumentError if included in changes. Supported value types: null, bool, int, double, String, List, and Map.
// Re-activate all inactive users
final updated = await db.users
    .filter()
    .activeEqualTo(false)
    .updateAll({'active': true});

print('Re-activated $updated users');
changes
Map<String, Object?>
required
Field names mapped to their new values. Supports null, bool, int, double, String, List, and Map. The id field cannot be updated.
updateAll requires a native MDBX binary query plan. Calling it when no native plan is available throws UnsupportedError.

updateFirst

Future<bool> updateFirst(Map<String, Object?> changes)
Applies the field updates in changes to the first document matching the query. Returns true if a document was updated, or false if no documents matched. Like updateAll, this requires a native query plan and does not support updating the id field.
final updated = await db.users
    .filter()
    .activeEqualTo(false)
    .sortByCreatedAt()
    .updateFirst({'active': true});

print(updated ? 'Re-activated one user' : 'No inactive users found');
changes
Map<String, Object?>
required
Field names mapped to their new values. The id field cannot be updated.

Modifiers

Modifiers are chained before a terminal operation. Each returns a new CindelQuery<T>.

sortBy / sortByDesc (generated)

Generated sort helpers are named after indexed fields — for example, sortByName() for ascending order and sortByNameDesc() for descending. Under the hood they call CindelQuery.sortBy(field, order: ...). You can also call sortBy directly on any query with a raw field name:
// Via generated helper (preferred)
final users = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .findAll();

// Via raw sortBy
final users = await db.users
    .all()
    .sortBy('name')
    .findAll();

// Descending
final newest = await db.users
    .all()
    .sortBy('createdAt', order: CindelSortOrder.descending)
    .findAll();
Multiple sort keys can be chained with thenBy for secondary and tertiary ordering:
final sorted = await db.users
    .all()
    .sortBy('name')
    .thenBy('createdAt', order: CindelSortOrder.descending)
    .findAll();

offset

CindelQuery<T> offset(int count)
Skips the first count results after filtering, sorting, and distinct are applied. Must be non-negative.
count
int
required
Number of results to skip. Must be ≥ 0.

limit

CindelQuery<T> limit(int count)
Returns at most count results after offset is applied. Must be non-negative.
count
int
required
Maximum number of results to return. Must be ≥ 0.

Pagination example

const pageSize = 20;
const page = 2; // 0-indexed

final users = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .offset(page * pageSize)
    .limit(pageSize)
    .findAll();

distinctBy / distinctByFields

CindelQuery<T> distinctBy(String field)
CindelQuery<T> distinctByFields(Iterable<String> fields)
Keeps only the first result for each distinct value of field, or for each distinct tuple of fields. De-duplication is applied after filtering and sorting, so the sort order determines which document is kept when multiple documents share the same distinct key.
// One result per unique name value
final uniqueNames = await db.users
    .all()
    .sortByName()
    .distinctBy('name')
    .findAll();

whereMatches

CindelQuery<T> whereMatches(CindelFilterPredicate predicate)
Returns a new query that additionally requires all results to match predicate. When the predicate is a top-level equality check on an indexed field and no other filter or source is already set, Cindel automatically promotes the query to an index-backed source. Use CindelFilter.field(name) or CindelFilter.path(parts) to build predicates, and CindelFilter.all, CindelFilter.any, and CindelFilter.not to compose them.
final predicate = CindelFilter.field('age').greaterThan(18);

final adults = await db.users
    .all()
    .whereMatches(predicate)
    .findAll();
predicate
CindelFilterPredicate
required
A filter predicate built with CindelFilter helpers. ANDed with any existing filter on the query.

Property Projections

Cindel exposes two projection APIs. Single-field projections use property<R>(field) (or generated helpers such as .nameProperty()) and return a CindelPropertyQuery<T, R>. Multi-field projections use properties(fields) and return a CindelPropertiesQuery<T>. Both avoid deserializing fields you do not need.

property / CindelPropertyQuery

CindelPropertyQuery<T, R> property<R>(
  String field, {
  R Function(Object? value)? decode,
})
Projects the query to a single field. Pass an optional decode callback to convert raw document values to a typed R. When the backing query has a native plan on MDBX, the projection and aggregates run entirely in native code.

findAll (property projection)

Future<List<R>> findAll()
Returns a list of projected field values for every matching document, in the same order as the parent query result.
// Collect every active user's name via a generated helper
final names = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .nameProperty()   // generated projection helper
    .findAll();       // Future<List<String?>>

// Or via the raw property() method
final names = await db.users
    .all()
    .property<String?>('name')
    .findAll();

findFirst (property projection)

Future<R?> findFirst()
Returns the first projected value, or null if no documents match.
final firstName = await db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .nameProperty()
    .findFirst();

count (property projection)

Future<int> count()
Returns the number of non-null projected values across all matching documents.
final namedCount = await db.users.all().nameProperty().count();

Aggregates

Aggregates operate over the projected field values of all matching documents, ignoring null values.
MethodReturn typeDescription
max()Future<R?>Largest non-null value.
min()Future<R?>Smallest non-null value.
sum()Future<num?>Sum of numeric values. Returns null when all values are null.
average()Future<double?>Mean of numeric values. Returns null when all values are null.
// Highest assigned id in the collection
final maxId = await db.users.all().dbIdProperty().max();

// Average age of active users
final avgAge = await db.users
    .filter()
    .activeEqualTo(true)
    .ageProperty()
    .average();

// Total order value
final total = await db.orders
    .filter()
    .statusEqualTo('completed')
    .amountProperty()
    .sum();
sum and average require the projected field to hold numeric values at runtime. Calling them on a non-numeric field throws CindelQueryError. max and min require the projected type to be Comparable.

properties / CindelPropertiesQuery

CindelPropertiesQuery<T> properties(Iterable<String> fields)
Projects the query to multiple fields and returns a CindelPropertiesQuery<T>. Each result is a CindelDocument (a Map<String, Object?>) containing only the requested keys.

findAll (multi-field projection)

Future<List<CindelDocument>> findAll()
Returns a list of partial documents containing only the projected fields, in the same order as the parent query result.
final partials = await db.users
    .filter()
    .activeEqualTo(true)
    .properties(['name', 'email'])
    .findAll();

for (final doc in partials) {
  print('${doc['name']}${doc['email']}');
}

findFirst (multi-field projection)

Future<CindelDocument?> findFirst()
Returns the first projected document, or null if no documents match.
final first = await db.users
    .all()
    .sortByName()
    .properties(['name', 'email'])
    .findFirst();

Reactive

watch

Stream<List<T>> watch({
  Duration pollInterval = defaultCindelWatchPollInterval,
  bool fireImmediately = true,
})
Returns a stream that re-executes the query and emits a new typed result list each time the collection changes in a way that could affect the visible result. Consecutive identical result sets are suppressed — the stream only fires when the actual result changes. By default the stream emits the current query result immediately on subscription. Set fireImmediately: false to suppress the initial event and only receive subsequent changes.
// Watch active users, sorted by name, live in a Flutter widget
db.users
    .filter()
    .activeEqualTo(true)
    .sortByName()
    .watch()
    .listen((users) {
      setState(() => _users = users);
    });
pollInterval
Duration
default:"defaultCindelWatchPollInterval"
How often the underlying change watcher polls for updates. Defaults to the global defaultCindelWatchPollInterval constant.
fireImmediately
bool
default:"true"
When true (the default), the stream emits the current query result immediately on subscription before listening for further changes. Set to false to receive only subsequent change events.

watchLazy

Stream<void> watchLazy({
  Duration pollInterval = defaultCindelWatchPollInterval,
  bool fireImmediately = false,
})
Returns an invalidation-only stream that emits a void event when the query result may have changed, without re-executing the query or deserializing any objects. Use this when you manage the read yourself and want to avoid the cost of automatic re-execution on every change.
db.users
    .filter()
    .activeEqualTo(true)
    .watchLazy()
    .listen((_) async {
      // Manually refresh cached state
      final fresh = await db.users.filter().activeEqualTo(true).findAll();
      _updateCache(fresh);
    });
pollInterval
Duration
default:"defaultCindelWatchPollInterval"
How often the underlying change watcher polls for updates.
fireImmediately
bool
default:"false"
When true, the stream emits one event immediately on subscription.

Composition Helpers

Composition helpers let you build dynamic queries without if branches. They are available on generated filter builders and proxy through to CindelQuery methods of the same name.

optional

CindelQuery<T> optional(
  bool condition,
  CindelQueryOption<T> builder,
)
Applies builder to the query only when condition is true. When condition is false, the query is returned unchanged. This is the idiomatic way to add a filter only when a search term or toggle is present.
final query = await db.users
    .filter()
    .optional(
      search.isNotEmpty,
      (q) => q.nameContains(search),
    )
    .findAll();
condition
bool
required
When true, builder is applied. When false, the query is unchanged.
builder
CindelQueryOption<T>
required
A callback that receives the current query and returns a modified query. May only add filter predicates — sort, distinct, window, or source changes are rejected.

anyOf

CindelQuery<T> anyOf<E>(
  Iterable<E> items,
  CindelQueryRepeatOption<T, E> builder,
)
Applies builder once for each item in items and ORs the generated predicates together. A document matches if it satisfies the predicate produced by any one item. If items is empty, the query matches nothing.
final selectedTags = ['flutter', 'dart'];

final users = await db.users
    .filter()
    .anyOf(
      selectedTags,
      (q, tag) => q.tagsElementEqualTo(tag),
    )
    .findAll();
items
Iterable<E>
required
Values to iterate. An empty iterable makes the query match nothing.
builder
CindelQueryRepeatOption<T, E>
required
A callback invoked once per item. Returns a query with one additional filter predicate. May only add filters — other modifications are rejected.

allOf

CindelQuery<T> allOf<E>(
  Iterable<E> items,
  CindelQueryRepeatOption<T, E> builder,
)
Applies builder once for each item in items and ANDs the generated predicates together. A document matches only if it satisfies the predicate produced by every item. If items is empty, the query is returned unchanged.
final requiredTags = ['verified', 'active'];

final users = await db.users
    .filter()
    .allOf(
      requiredTags,
      (q, tag) => q.tagsElementEqualTo(tag),
    )
    .findAll();
items
Iterable<E>
required
Values to iterate. An empty iterable leaves the query unchanged.
builder
CindelQueryRepeatOption<T, E>
required
A callback invoked once per item. Returns a query with one additional filter predicate. May only add filters — other modifications are rejected.

Build docs developers (and LLMs) love