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’s migration system lets you advance your local database schema safely across app versions. You pass a CindelMigrationPlan to Cindel.open on every app start; Cindel reads the stored data version, skips steps that have already completed, and opens the final database handle with the target schemas only after every pending step succeeds. The same migration contract is supported across the SQLite native, MDBX, and SQLite Web/OPFS backends.

CindelMigrationPlan

CindelMigrationPlan describes the full upgrade path from any historical data version to the current targetVersion. Pass one instance to Cindel.open via the migrationPlan parameter on every cold start — Cindel is idempotent and simply skips steps whose toVersion is already recorded in the database.
final migrations = CindelMigrationPlan(
  targetVersion: 2,
  baselineVersion: 1,
  steps: [
    CindelMigrationStep(
      fromVersion: 1,
      toVersion: 2,
      openSchemas: [OldUserSchema],
      targetSchemas: [UserSchema],
      migrate: (context) async {
        final oldUsers = await context.exportObjects(OldUserSchema);
        await context.registerTargetSchemas();
        await context.importObjects(
          UserSchema,
          oldUsers.map((old) => User.fromLegacy(old)),
        );
      },
    ),
  ],
);

Constructor Parameters

targetVersion
int
required
Final data version expected after all migration steps complete. Must be non-negative. Cindel throws an ArgumentError at construction time if a negative value is supplied.
steps
Iterable<CindelMigrationStep>
required
Ordered list of migration steps. Each step is matched by its fromVersion value, so steps may be declared in any iterable order, but there must be exactly one step for each version transition that exists between the stored version and targetVersion.
baselineVersion
int
default:"0"
Version assumed for a database that already has a persisted schema but no migration version marker — typically an app that was shipped before CindelMigrationPlan was introduced. Setting this to the version your first deployed schema represents prevents Cindel from re-running those steps for existing users. Must be non-negative.
compactOnSuccess
bool
default:"true"
When true, Cindel compacts the storage backend after each step completes successfully. Compaction reclaims space freed during data rewrites and is recommended for steps that delete or replace large volumes of documents.

Methods

run({directory, targetSchemas, backend})

Future<void> run({
  required String directory,
  required Iterable<CindelCollectionSchema<dynamic>> targetSchemas,
  required Object backend,
})
Executes the migration plan. This method is public so the native and Web database facades can share one implementation, but application code should pass the plan to Cindel.open rather than calling run directly. Cindel opens a metadata database to determine the current version, then iterates through each pending step in order, invoking verifyBefore, migrate, registerTargetSchemas (if not already called), and verifyAfter for every step before persisting the new version number.

CindelMigrationStep

CindelMigrationStep represents a single version transition. Each step declares the schemas needed to read the current data (openSchemas) and optionally the schemas needed to write the migrated data (targetSchemas). It also carries up to three callbacks: an optional pre-check, the required migration body, and an optional post-check.

Constructor Parameters

fromVersion
int
required
Data version expected at the start of this step. Cindel uses this value to look up the correct step when iterating through a plan — it must match the version stored (or derived) in the database.
toVersion
int
required
Data version persisted after this step succeeds. Must be strictly greater than fromVersion. Cindel throws a StateError at plan-execution time if this constraint is violated.
openSchemas
Iterable<CindelCollectionSchema>
required
Schemas used to open the database before rewriting data. These schemas must describe the shape of data currently on disk so that exportObjects and exportDocuments can deserialize existing records correctly.
targetSchemas
Iterable<CindelCollectionSchema>?
Schemas that will be registered by CindelMigrationContext.registerTargetSchemas. When omitted, the step inherits the targetSchemas passed to Cindel.open, which is the most common case. Supply an explicit value only when a step’s intermediate target shape differs from the final app schema.
verifyBefore
CindelMigrationCallback?
Optional callback executed before migrate. Use it to assert invariants on the existing data — for example, checking that expected collections exist or that document counts are within expected ranges. If this callback throws, the step is aborted and the database version is not advanced.
migrate
CindelMigrationCallback
required
Main migration callback. It receives a CindelMigrationContext and is responsible for exporting old data, calling registerTargetSchemas, and importing the rewritten data. If registerTargetSchemas has not been called by the time migrate returns, Cindel calls it automatically before advancing the version.
verifyAfter
CindelMigrationCallback?
Optional callback executed after migrate and registerTargetSchemas have both completed. Use it to confirm the resulting state — for example, reading back a schema version or asserting that key documents were written.

Callback Signature

All three callbacks share the same typedef:
typedef CindelMigrationCallback =
    FutureOr<void> Function(CindelMigrationContext context);

CindelMigrationContext

CindelMigrationContext is passed to every migration callback. It exposes the open database handle for the current step along with version information and the full set of data-transfer helpers needed to read old records and write new ones.

Properties

database
CindelDatabase
Open database handle for the running migration step. The handle is opened with openSchemas so you can read existing documents through the old typed collections. Do not close this handle manually — Cindel closes it after the step completes.
fromVersion
int
Source data version for this step; mirrors CindelMigrationStep.fromVersion.
toVersion
int
Target data version for this step; mirrors CindelMigrationStep.toVersion.
targetSchemas
List<CindelCollectionSchema>
Schemas that will be registered as the new target shape when registerTargetSchemas is called. Read-only; set by the step or inherited from Cindel.open.
targetSchemasRegistered
bool
true once registerTargetSchemas has completed for this step. Cindel checks this flag after migrate returns and calls registerTargetSchemas automatically if it is still false.

Methods

registerTargetSchemas()Future<void>

Future<void> registerTargetSchemas()
Clears the target collection storage and registers the new schemas so that subsequent importObjects or importDocuments calls write into the migrated layout. This method is idempotent — calling it more than once is safe and subsequent calls are no-ops. You must call it (or allow Cindel to call it automatically) before importing data in the new format.

exportObjects<T>(schema, {batchSize})Future<List<T>>

Future<List<T>> exportObjects<T>(
  CindelCollectionSchema<T> schema, {
  int batchSize = 100,
})
Reads all typed objects in the collection described by schema in id order, fetching batchSize ids at a time. Returns a flat List<T> containing every non-null object found. Throws an ArgumentError if batchSize is less than or equal to zero.
ParameterTypeDefaultDescription
schemaCindelCollectionSchema<T>Schema used to deserialize existing objects
batchSizeint100Number of ids fetched per batch

exportDocuments<T>(schema, {batchSize})Future<List<CindelDocument>>

Future<List<CindelDocument>> exportDocuments<T>(
  CindelCollectionSchema<T> schema, {
  int batchSize = 100,
})
Same as exportObjects but returns raw CindelDocument maps (i.e. Map<String, Object?>) with the id field included. Useful when you want to inspect or transform document fields directly without working through the typed model.
ParameterTypeDefaultDescription
schemaCindelCollectionSchema<T>Schema used to deserialize existing objects
batchSizeint100Number of ids fetched per batch

importObjects<T>(schema, objects, {batchSize})Future<void>

Future<void> importObjects<T>(
  CindelCollectionSchema<T> schema,
  Iterable<T> objects, {
  int batchSize = 100,
})
Bulk-writes rewritten typed objects into schema in batches of batchSize, each batch executed inside a write transaction. Call registerTargetSchemas before calling this method so the target collection is registered in migrated mode. Throws an ArgumentError if batchSize is less than or equal to zero.
ParameterTypeDefaultDescription
schemaCindelCollectionSchema<T>Target schema to write into
objectsIterable<T>Rewritten typed objects
batchSizeint100Objects written per transaction

importDocuments<T>(schema, documents, {batchSize})Future<void>

Future<void> importDocuments<T>(
  CindelCollectionSchema<T> schema,
  Iterable<CindelDocument> documents, {
  int batchSize = 100,
})
Imports raw CindelDocument maps into schema by deserializing each map with schema.fromDocument, restoring the stored id when schema.setId is available, and delegating to importObjects in batches. Use this when your export step used exportDocuments or when you are building documents from external sources.
ParameterTypeDefaultDescription
schemaCindelCollectionSchema<T>Target schema to write into
documentsIterable<CindelDocument>Raw map documents including the id field
batchSizeint100Documents written per transaction

Complete Migration Example

The following example is drawn from the README. It shows a full two-step plan with pre- and post-verification, advancing a database from version 1 to version 2 by rewriting OldUser records as the new User shape.
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 accessible before starting.
        await context.database.documentIds('users');
      },
      migrate: (context) async {
        // 1. Export all existing typed objects using the old schema.
        final oldUsers = await context.exportObjects(OldUserSchema);

        // 2. Register the new target schemas (clears old storage).
        await context.registerTargetSchemas();

        // 3. Import rewritten objects using the new schema.
        await context.importObjects(
          UserSchema,
          oldUsers.map((old) => User.fromLegacy(old)),
        );
      },
      verifyAfter: (context) async {
        // Confirm the new schema version was persisted.
        await context.database.schemaVersion('users');
      },
    ),
  ],
);

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

Build docs developers (and LLMs) love