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.

Whenever you add a field, rename a collection, change a field type, or make any other structural change to your Cindel models, existing databases on user devices hold data in the old layout. Cindel’s migration system lets you declare a versioned plan that is evaluated every time the database opens. Completed steps are skipped automatically, and the final database handle is only returned after all applicable steps have succeeded.

Why Migrations

Dart code regenerated after a schema change understands the new field layout, but on-device databases still contain documents written with the old layout. Without a migration step the mismatch between stored bytes and the new schema reader can corrupt objects or silently drop fields. A migration step opens the database with the old schema, exports data, rewrites it to match the new schema, and replaces the stored documents before the application ever reads them.

CindelMigrationPlan

Create a CindelMigrationPlan and pass it to Cindel.open on every application start:
final migrations = CindelMigrationPlan(
  targetVersion: 2,
  baselineVersion: 1,
  steps: [
    CindelMigrationStep(
      fromVersion: 1,
      toVersion: 2,
      openSchemas: [OldUserSchema],
      targetSchemas: [UserSchema],
      verifyBefore: (context) async {
        await context.database.documentIds('users');
      },
      migrate: (context) async {
        final oldUsers = await context.exportObjects(OldUserSchema);
        await context.registerTargetSchemas();
        await context.importObjects(
          UserSchema,
          oldUsers.map((old) => User.fromLegacy(old)),
        );
      },
      verifyAfter: (context) async {
        await context.database.schemaVersion('users');
      },
    ),
  ],
);

final db = await Cindel.open(
  directory: directory.path,
  schemas: [UserSchema],
  migrationPlan: migrations,
);

Parameters

ParameterDescription
targetVersionThe final data version expected after all steps complete. Must not be negative.
stepsOrdered list of CindelMigrationStep values. Cindel selects the step whose fromVersion matches the current stored version.
baselineVersionVersion to assume for a database that already has schemas but no stored migration marker. Defaults to 0.
compactOnSuccessCompact the backend after a successful migration run. Defaults to true.

Version Tracking

Cindel stores the data version inside the database itself. On open it reads the stored version, compares it to targetVersion, and runs only the steps whose fromVersion matches the current version in order. A fresh database with no schemas and no version marker is initialized directly to targetVersion so no steps run for new installs. The baselineVersion parameter handles databases that were created before you introduced migrations. If the database has schemas but no version marker, Cindel treats it as being at baselineVersion and runs any applicable steps from there.

CindelMigrationStep

Each step advances the database from one version to the next:
ParameterDescription
fromVersionData version before this step runs.
toVersionData version persisted after this step succeeds. Must be greater than fromVersion.
openSchemasSchemas used to open and read the database in its pre-migration layout.
targetSchemasSchemas registered by registerTargetSchemas. Defaults to the top-level targetSchemas from Cindel.open.
verifyBeforeOptional callback called before migrate. Use it to assert preconditions on the old data.
migrateRequired callback that exports old data, registers the new schema, and imports rewritten data.
verifyAfterOptional callback called after migrate. Use it to assert postconditions on the new data.

CindelMigrationContext

The context object is passed to every callback in a step. It provides the open database handle and helpers for reading and writing documents:

context.database

The CindelDatabase handle opened with openSchemas. You can call documentIds, schemaVersion, or use any other low-level database API on it.

context.exportObjects(schema)

Reads all typed objects from a collection in id order. Returns a List<T> using the provided schema’s deserializer. Internally batches reads to avoid loading the entire collection into memory at once:
final oldUsers = await context.exportObjects(OldUserSchema);

context.exportDocuments(schema)

Like exportObjects, but returns each object as a raw Map<String, Object?>. Useful when you want to inspect or transform documents without a fully typed old model class:
final rawUsers = await context.exportDocuments(OldUserSchema);

context.registerTargetSchemas()

Clears the target collection storage and registers the new schema, preparing the collection to accept documents in the new layout. Call this after exporting old data and before importing rewritten data.
registerTargetSchemas() clears the storage for all target collections. Always export your data with exportObjects or exportDocuments before calling registerTargetSchemas(), or you will permanently lose the existing documents.

context.importObjects(schema, objects)

Bulk-writes a list of typed objects into the target collection in batches. The objects must conform to the targetSchemas registered by registerTargetSchemas():
await context.importObjects(
  UserSchema,
  oldUsers.map((old) => User.fromLegacy(old)),
);

context.importDocuments(schema, documents)

Like importObjects, but accepts raw maps. Cindel uses the schema’s fromDocument deserializer and preserves the original id from the map’s id field when present.

Complete Migration Example

The example below migrates a users collection from OldUserSchema (version 1) to UserSchema (version 2). The User.fromLegacy constructor on the new class accepts an OldUser and maps its fields to the new layout:
final migrations = CindelMigrationPlan(
  targetVersion: 2,
  baselineVersion: 1,
  steps: [
    CindelMigrationStep(
      fromVersion: 1,
      toVersion: 2,
      openSchemas: [OldUserSchema],
      targetSchemas: [UserSchema],
      verifyBefore: (context) async {
        // Confirm the old collection is readable before touching anything.
        await context.database.documentIds('users');
      },
      migrate: (context) async {
        // 1. Export all existing objects using the old schema.
        final oldUsers = await context.exportObjects(OldUserSchema);

        // 2. Clear old storage and register the new schema.
        await context.registerTargetSchemas();

        // 3. Write rewritten objects into the new collection.
        await context.importObjects(
          UserSchema,
          oldUsers.map((old) => User.fromLegacy(old)),
        );
      },
      verifyAfter: (context) async {
        // Confirm the new schema is readable after migration.
        await context.database.schemaVersion('users');
      },
    ),
  ],
);

final db = await Cindel.open(
  directory: directory.path,
  schemas: [UserSchema],
  migrationPlan: migrations,
);

Compaction

When compactOnSuccess is true (the default), Cindel calls the backend’s compact operation after each step succeeds. Compaction reclaims space freed by replacing or deleting old documents and is especially useful after migrations that rewrite large collections.

Build docs developers (and LLMs) love