> ## Documentation Index
> Fetch the complete documentation index at: https://cindel.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining Cindel Collections, IDs, Fields, and Indexes

> Use @Collection and @Index to define root collections, declare supported field types, control stored names, and create composite indexes for fast queries.

A Cindel collection is the Dart class that becomes a stored table in your database. You annotate the class, declare its fields, run code generation, and Cindel produces a typed schema and query API ready to use in your Flutter app. This page walks through every modeling decision you will encounter: collection annotations, IDs, persisted field types, ignored fields, enums, and indexes.

## Declaring a Collection

Use `@Collection()` to mark a Dart class as a Cindel root collection. Pass a `name:` argument to set an explicit stored name, or omit it to let the generator derive the name from the class name.

```dart theme={null}
import 'package:cindel/cindel.dart';

part 'user.g.dart';

@Collection(name: 'users')
class User {
  Id dbId = autoIncrement;

  @Index(unique: true)
  late String email;

  late String name;
  bool active = true;
}
```

After code generation this model produces:

* a schema constant (`UserSchema`),
* a typed collection accessor on the database instance (`db.users`),
* generated read, write, and query helpers for `User`.

The `name` parameter is the **persisted** collection name. Cindel stores it with the database and uses it for every open, migration, backup, and restore. Keep it stable once you have real user data — renaming the Dart class is safe, but changing the stored name changes the format your database expects.

### `@collection` Shorthand

When you are happy with a derived name, use the lowercase `@collection` constant — it is equivalent to `@Collection()` with no arguments.

```dart theme={null}
@collection
class Project {
  Id dbId = autoIncrement;
  late String title;
}
```

Cindel derives the collection name from the class name. This is fine for prototypes and tests. Prefer `@Collection(name: ...)` when you want the stored name to be explicit and stable.

### Controlling Stored Names with `@Name`

Use `@Name` to decouple the stored name from the Dart identifier on either a class or a field.

```dart theme={null}
@Name('accounts')
@collection
class Account {
  Id dbId = autoIncrement;

  @Name('user_name')
  @Index(unique: true, replace: true)
  late String username;
}
```

In Dart you still reference `Account`, `AccountSchema`, `db.accounts`, and `username`. In storage the collection is named `accounts` and the field is named `user_name`. This lets you improve Dart naming freely while keeping the stored format stable for existing data.

<Note>
  Every Cindel collection class requires a `part` directive pointing to the generated file — for example `part 'user.g.dart';`. Without it, code generation output will not be found and your project will not compile.
</Note>

***

## IDs

Every root collection needs exactly one persisted id field named `dbId` with type `Id`.

### Auto-Increment IDs

Use `autoIncrement` when Cindel should assign the ID on first write.

```dart theme={null}
@collection
class Task {
  Id dbId = autoIncrement;
  late String title;
}
```

After calling `put`, the generated ID setter writes the allocated value back into the object:

```dart theme={null}
final task = Task()..title = 'Write docs';
await db.tasks.put(task);

print(task.dbId); // assigned by Cindel
```

This is the standard style for mutable model classes.

### Explicit IDs

Use an explicit ID when your app owns the value — for example imported data, deterministic fixtures, or records whose ID is assigned by a server.

```dart theme={null}
@collection
class Category {
  Category({required this.dbId, required this.name});

  final Id dbId;
  final String name;
}

final category = Category(dbId: 42, name: 'Books');
await db.categories.put(category);
```

***

## Persisted Field Types

Cindel persists every field whose type is in the supported set and that is not marked `@ignore`. Supported field shapes are:

| Category          | Examples                                     |
| ----------------- | -------------------------------------------- |
| Primitives        | `bool`, `int`, `double`, `String`            |
| Date / time       | `DateTime`, `Duration`                       |
| Enums             | any `enum` with `@Enumerated`                |
| Embedded objects  | any class annotated `@embedded`              |
| Nullable variants | `String?`, `DateTime?`, embedded?            |
| Flat lists        | `List<String>`, `List<int>`, `List<Address>` |

```dart theme={null}
@collection
class Task {
  Id dbId = autoIncrement;

  late String title;
  bool completed = false;
  int priority = 0;
  DateTime createdAt = DateTime.now().toUtc();
  Duration? reminderAfter;
  List<String> tags = const [];
}
```

Nullable fields work for any supported underlying type:

```dart theme={null}
@collection
class Profile {
  Id dbId = autoIncrement;

  String? displayName;
  DateTime? birthday;
}
```

**Nested lists are not supported.** A `List<List<int>>` field will not be persisted. Use embedded objects for structured child values and a separate root collection for independently stored objects.

***

## Ignoring Fields

Use `@ignore` (or `@Ignore()`) to exclude a field from persistence entirely. The field exists in Dart but Cindel will never read or write it.

```dart theme={null}
@collection
class User {
  Id dbId = autoIncrement;

  late String email;

  @ignore
  String runtimeOnlyLabel = '';
}
```

<Tip>
  Ignored fields are useful for UI-only labels, temporary selection state, derived values, caches, and anything that does not need to survive a database close and reopen. Do not mark real app data as ignored — if a value must survive a restart, model it as a supported persisted field.
</Tip>

***

## Enums

Cindel persists enum fields when you annotate them with `@Enumerated` and choose a storage strategy.

### Name-Based Storage

`CindelEnumType.name` stores the enum case name as a string. This is the easiest strategy to read and debug.

```dart theme={null}
enum UserRole { admin, editor, viewer }

@collection
class User {
  Id dbId = autoIncrement;

  @Enumerated(CindelEnumType.name)
  UserRole role = UserRole.viewer;
}
```

Treat enum case names as stored data once the app has persisted records — renaming a case is a breaking change.

### Ordinal-Based Storage

`CindelEnumType.ordinal` stores the enum case index (0, 1, 2 …). It is compact, but enum declaration order becomes part of the stored contract.

```dart theme={null}
enum Priority { low, normal, high }

@collection
class Task {
  Id dbId = autoIncrement;

  @Enumerated(CindelEnumType.ordinal)
  Priority priority = Priority.normal;
}
```

Only use ordinal storage when you are confident the declaration order will never change.

### Value-Based Storage

`CindelEnumType.value` stores the value of a named instance field. Use this when your enum cases carry durable external codes.

```dart theme={null}
enum AccountStatus {
  active('A'),
  suspended('S');

  const AccountStatus(this.code);
  final String code;
}

@collection
class Account {
  Id dbId = autoIncrement;

  @Enumerated(CindelEnumType.value, valueField: 'code')
  AccountStatus status = AccountStatus.active;
}
```

***

## Indexes

Indexes speed up queries and let you enforce uniqueness constraints. Cindel provides single-field indexes and class-level composite indexes.

### Single-Field Index

Place `@Index` (or the lowercase `@index` constant) directly on a field.

```dart theme={null}
@Collection(name: 'users')
class User {
  Id dbId = autoIncrement;

  @Index(unique: true)
  late String email;

  @Index(type: CindelIndexType.words)
  late String name;
}
```

`@Index` options:

| Option          | Type              | Default  | Description                                                         |
| --------------- | ----------------- | -------- | ------------------------------------------------------------------- |
| `unique`        | `bool`            | `false`  | Rejects writes that would create a duplicate value                  |
| `replace`       | `bool`            | `false`  | Replaces the existing document on a unique-index violation (upsert) |
| `caseSensitive` | `bool`            | `true`   | Case-sensitive string comparison (string fields only)               |
| `type`          | `CindelIndexType` | `.value` | Storage strategy (see below)                                        |

### Index Types

| Type                         | Use when                                                               |
| ---------------------------- | ---------------------------------------------------------------------- |
| `CindelIndexType.value`      | You need sortable range queries (`greaterThan`, `between`, `lessThan`) |
| `CindelIndexType.hash`       | You need equality queries only and want a smaller index                |
| `CindelIndexType.words`      | You need token or prefix search on a string field                      |
| `CindelIndexType.multiEntry` | You need list-membership queries on a list field                       |

### Composite Index

Declare composite indexes at the class level using the `indexes` parameter of `@Collection`.

```dart theme={null}
@Collection(
  name: 'products',
  indexes: [
    CompositeIndex(
      ['category', 'name'],
      unique: true,
      caseSensitive: false,
    ),
  ],
)
class Product {
  Id dbId = autoIncrement;

  late String category;
  late String name;
  late double price;
}
```

`CompositeIndex` takes a list of field names in the order they form the composite key. It supports the same `unique`, `replace`, and `caseSensitive` options as `@Index`.

***

## Modeling Checklist

Before running code generation, verify each collection from your app's point of view:

<Steps>
  <Step title="Correct annotation">
    The class has `@Collection(name: ...)` or `@collection`, and a `part` directive for the generated file.
  </Step>

  <Step title="One persisted ID">
    There is exactly one `Id dbId` field, set to `autoIncrement` or an explicit value as appropriate.
  </Step>

  <Step title="Supported field types only">
    Every persisted field uses a supported type. Runtime-only fields are marked `@ignore`.
  </Step>

  <Step title="Stable stored names">
    `@Collection(name: ...)` and `@Name(...)` values are stable if real user data already exists.
  </Step>

  <Step title="Enum strategies confirmed">
    Each `@Enumerated` field uses a strategy whose contract (case names, ordinal positions, or value fields) is stable.
  </Step>

  <Step title="Indexes match query patterns">
    Every field used in a sorted, range, or equality query has the appropriate index type.
  </Step>
</Steps>
