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

# Cindel Error Reference: Causes and Recovery Actions

> Reference for all Cindel error classes — open, schema, transaction, query, unique-index, and native errors — with causes and recovery actions.

Cindel uses specific, named error types so you can respond precisely to actionable conditions — distinguishing a duplicate-key violation from a storage failure from a programming mistake — rather than catching a generic exception and guessing what went wrong. All Cindel-specific runtime errors extend `StateError` and share the `CindelError` base class. Catch the most specific type you can, and let everything else surface to your normal error-reporting pipeline.

<AccordionGroup>
  <Accordion title="CindelOpenError">
    `CindelOpenError` is thrown when the database backend cannot be opened. You will encounter this error at startup, inside your `Cindel.open(...)` call, never during normal query or write operations.

    **Common causes**

    * The `directory` path does not exist or is invalid
    * The application does not have write permission for that directory
    * A required platform dependency is missing
    * On Web: the browser does not support the required storage features (OPFS/SQLite Web), private-browsing mode is active, or the storage quota has been exceeded

    **Recovery**

    Check that the target directory exists and is writable before calling `open`. On Web, detect storage unavailability early and show a graceful message rather than letting the error propagate silently.

    ```dart theme={null}
    try {
      final db = await Cindel.open(
        directory: directory.path,
        schemas: [UserSchema],
      );
    } on CindelOpenError catch (e) {
      // Storage is unavailable — inform the user and abort startup.
      print('Cannot open database: $e');
      showStorageUnavailableMessage();
    }
    ```
  </Accordion>

  <Accordion title="CindelSchemaError">
    `CindelSchemaError` is thrown when the schemas you pass at open time don't match the metadata already stored on disk. Like `CindelOpenError`, this surfaces during `Cindel.open(...)`.

    **Common causes**

    * A required schema is missing from the `schemas:` list
    * You changed your data model but forgot to re-run `build_runner`, so the generated code is stale
    * Stored data was written with a different schema version and no migration plan was provided

    **Recovery**

    Make sure every schema your database uses is listed in `schemas:`. After any model change, re-run code generation before opening the database. When stored data must be rewritten to match a new model shape, provide a `CindelMigrationPlan`.

    ```dart theme={null}
    try {
      final db = await Cindel.open(
        directory: directory.path,
        schemas: [UserSchema, ProjectSchema],
      );
    } on CindelSchemaError catch (e) {
      // Schema registration or compatibility failure — check for missing
      // schemas, stale generated code, or a missing migration plan.
      showDataUpgradeFailedMessage();
    }
    ```
  </Accordion>

  <Accordion title="CindelDatabaseClosedError">
    `CindelDatabaseClosedError` is thrown when code attempts to use a database instance after `close()` has already been called on it.

    **Common causes**

    * Holding a reference to a database and using it after the owning component has already shut down
    * Calling `close()` then reusing the same handle instead of reopening

    **Recovery**

    Make ownership clear: the component that opens the database decides when it is closed. Avoid sharing a closed database handle. If you need the database again after closing, open a fresh instance.

    ```dart theme={null}
    await db.close();

    // ❌ This throws CindelDatabaseClosedError:
    await db.users.all().findAll();
    ```
  </Accordion>

  <Accordion title="CindelTransactionError">
    `CindelTransactionError` is thrown when an invalid transaction operation is attempted at runtime.

    **Common causes**

    * Calling a write operation (e.g. `put`, `delete`) inside a `readTxn` callback
    * Starting nested explicit write transactions
    * Using a transaction object after it is already in an invalid or closed state

    **Recovery**

    This error almost always indicates a structural problem in your code rather than a user-recoverable condition. Use `readTxn` for read-only work and `writeTxn` for writes, and keep them separate. If you see this error, restructure the offending code path — don't catch and suppress it.

    ```dart theme={null}
    // ❌ This throws CindelTransactionError:
    await db.readTxn(() async {
      await db.todos.put(todo); // Write inside a read transaction.
    });

    // ✅ Correct:
    await db.writeTxn(() async {
      await db.todos.put(todo);
    });
    ```
  </Accordion>

  <Accordion title="CindelQueryError">
    `CindelQueryError` is thrown when a query shape is invalid or unsupported at runtime.

    **Common causes**

    * Using a property aggregate (e.g. `average()`) against a field that holds non-numeric values
    * Applying an unsupported query update shape
    * Composing query helpers in a way that produces an invalid query at runtime

    **Recovery**

    Use the generated typed `where()` and `filter()` helpers — they are shaped to match your model's actual field types. When using property aggregates, make sure the target field is numeric.

    ```dart theme={null}
    try {
      final average = await db.todos
          .all()
          .titleProperty()
          .average();
    } on CindelQueryError catch (e) {
      // The aggregate requires numeric values — check the field type.
      print('Query error: $e');
    }
    ```
  </Accordion>

  <Accordion title="CindelUniqueIndexError">
    `CindelUniqueIndexError` is thrown when an insert or update would violate a unique index — for example, storing two accounts with the same email address.

    **Recovery**

    Catch this error at the call site where you perform the write and surface a user-friendly message. This is the intended recovery path for natural-key conflicts such as email addresses, usernames, SKUs, or slugs.

    ```dart theme={null}
    try {
      await db.writeTxn(() => db.accounts.put(account));
    } on CindelUniqueIndexError {
      showDuplicateUsernameMessage();
    }
    ```

    If you want duplicate writes to silently replace the existing record instead of failing, annotate the field with `@Index(unique: true, replace: true)` and use the generated `putBy…` helper rather than catching this error:

    ```dart theme={null}
    @collection
    class Account {
      // Duplicate emails silently overwrite the existing record.
      @Index(unique: true, replace: true)
      late String email;
      // ...
    }

    // No exception — the existing account is replaced:
    await db.writeTxn(() => db.accounts.putByEmail(account));
    ```
  </Accordion>

  <Accordion title="CindelNativeError">
    `CindelNativeError` is thrown when Cindel receives invalid or unexpected native data, or when a native-facing operation returns a result it cannot interpret.

    **Recovery**

    This error is not a user-recoverable condition — it indicates a bug or data corruption. Do not catch it to continue silently. Instead, log the error with full context (operation name, relevant IDs, stack trace) and report it so you can investigate and fix the root cause.

    ```dart theme={null}
    try {
      final count = await db.todos.all().count();
    } on CindelNativeError catch (error, stackTrace) {
      // Log and report — this is unexpected and needs investigation.
      reportError(error, stackTrace);
    }
    ```
  </Accordion>
</AccordionGroup>

## General Handling Strategy

Catch specific errors at the point in your code where you can act on them. Handle open and schema errors during startup; handle unique-index errors at individual write call sites.

```dart theme={null}
// At startup:
try {
  return await Cindel.open(
    directory: directory.path,
    schemas: [UserSchema],
  );
} on CindelOpenError {
  showStorageUnavailableMessage();
} on CindelSchemaError {
  showDataUpgradeFailedMessage();
}

// At a write call site:
try {
  await db.writeTxn(() => db.users.put(user));
} on CindelUniqueIndexError {
  // Show user-friendly duplicate message.
} on CindelTransactionError {
  // Bug: restructure your code — reads and writes must not be mixed.
}
```

<Warning>
  Avoid broad `catch` blocks like `catch (e)` or `on CindelError`. They hide programming mistakes — such as transaction violations or schema mismatches — that should be fixed in code, not swallowed at runtime. Catch specific error types, and let unhandled errors reach your logging and crash-reporting infrastructure.
</Warning>
