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 includes an experimental local-first sync engine that you opt into at database open time. Once sync is configured, your application code continues to use the same typed collections, queries, transactions, and watchers as any non-sync database — Cindel records local mutations in an internal durable outbox and runs a background scheduler against the adapter you provide. You supply the backend-specific push and pull logic; Cindel handles sequencing, checkpointing, and applying remote changes.
This is an early integration API, not a complete production conflict-resolution layer. Before using it for critical data, validate your backend’s idempotency, checkpoint, rejection, reset, and conflict policies with your own app flows. The sync API may change before it is marked stable.

Enabling sync at open time

Sync has no runtime command surface — there is no db.sync property to call. Configure it once in Cindel.open using CindelSyncConfig:
final db = await Cindel.open(
  directory: dir.path,
  schemas: [UserSchema],
  sync: CindelSyncConfig(
    adapter: AppSyncAdapter(),
    onStatusChanged: (status) {
      // Show offline/syncing/pending state in your UI.
    },
    onError: (error, stackTrace) {
      // Report background sync failures.
    },
  ),
);
App code uses the same typed collections, queries, transactions, and watchers whether sync is enabled or not. Sync is purely an internal concern — db.users.put(user) records the mutation in the outbox and returns normally, just as it would without sync.

CindelSyncConfig parameters

ParameterTypeDefaultDescription
adapterCindelSyncAdapterrequiredApplication-supplied push/pull implementation
clientIdString?nullStable client id for idempotent backend writes. When omitted, Cindel persists and reuses an internal id
onStatusChangedvoid Function(CindelSyncStatus)?nullObserves status changes for UI feedback
onErrorvoid Function(Object, StackTrace)?nullObserves background sync errors
autoStartbooltrueStarts the internal scheduler immediately after open
intervalDurationDuration(seconds: 5)Background scheduler poll interval
batchSizeint100Maximum pending mutations sent in one push

Implementing CindelSyncAdapter

The adapter is an interface with two methods. You implement it using your backend’s HTTP client, WebSocket, or any other transport.
abstract interface class CindelSyncAdapter {
  Future<CindelPullResult> pull(CindelPullRequest request);
  Future<CindelPushResult> push(CindelPushRequest request);
}

Example adapter

final class AppSyncAdapter implements CindelSyncAdapter {
  @override
  Future<CindelPushResult> push(CindelPushRequest request) async {
    // Send request.mutations to your backend idempotently.
    return CindelPushResult(acceptedMutationIds: {
      for (final mutation in request.mutations) mutation.mutationId,
    });
  }

  @override
  Future<CindelPullResult> pull(CindelPullRequest request) async {
    // Return backend changes after request.checkpoint.
    return const CindelPullResult(checkpoint: '0', changes: []);
  }
}

Push: CindelPushRequest and CindelPushResult

The push direction sends local mutations from the durable outbox to your backend. CindelPushRequest
FieldTypeDescription
clientIdStringStable client id for this database
lastPulledCheckpointString?Last checkpoint successfully pulled before this push
schemaVersionByCollectionMap<String, int>Registered schema version per collection
mutationsList<CindelSyncMutation>Up to batchSize pending local mutations
CindelPushResult
FieldTypeDescription
acceptedMutationIdsSet<String>Mutation ids the backend has durably accepted
rejectedMutationsList<CindelSyncRejectedMutation>Mutations the backend will never accept
correctedChangesList<CindelRemoteChange>Backend truth to apply locally after accepting optimistic mutations
checkpointString?Optional checkpoint produced while handling this push

Pull: CindelPullRequest and CindelPullResult

The pull direction fetches remote changes from your backend after a known checkpoint. CindelPullRequest
FieldTypeDescription
clientIdStringStable client id for this database
checkpointString?Last checkpoint applied locally, or null for an initial pull
schemaVersionByCollectionMap<String, int>Registered schema version per collection
collectionsSet<String>Application collections this database can apply
CindelPullResult
FieldTypeDescription
checkpointStringNew checkpoint that covers all returned changes
changesList<CindelRemoteChange>Remote changes after the requested checkpoint
resetRequiredboolWhether the backend requires the client to reset before continuing

Sync mutations

Each item in CindelPushRequest.mutations is a CindelSyncMutation:
FieldDescription
mutationIdStable id used by the backend to deduplicate retries
clientIdClient that created this mutation
sequenceMonotonic local sequence for this client
collectionApplication collection affected
operationCindelSyncOperation.upsert, .delete, or .replaceLinks
documentIdDocument id for document operations
documentCanonical document snapshot for upserts
linkNameLink field name for link replacement operations
targetCollectionTarget collection for link replacement operations
targetIdsTarget ids for link replacement operations
baseCheckpointCheckpoint known when the local mutation was recorded

What is captured as a sync mutation

Cindel captures the following local operations into the durable outbox:
  • put (typed document upsert)
  • putAll (bulk upsert)
  • Unique-index putBy… upserts
  • delete and deleteAll (document deletes)
  • Query-based deleteAll (query deletes)
  • Link replacements (.save() on CindelLink / CindelLinks)

What is NOT supported while sync is enabled

Query updates — filter().updateAll({...}) — are explicitly rejected when sync is enabled. They do not produce canonical per-document mutations and cannot be represented in the outbox.

Sync status and phase

onStatusChanged receives a CindelSyncStatus on every scheduler transition:
FieldDescription
phaseCurrent CindelSyncPhase
pendingCountNumber of durable local mutations still waiting for adapter acceptance
lastSyncAtUTC timestamp of the last completed sync cycle, or null before one succeeds
lastErrorLast background error reported for this status event
CindelSyncPhase values:
PhaseMeaning
idleScheduler is waiting for the next interval
syncingScheduler is actively running push or pull
offlineAdapter threw a connectivity-class error
errorAdapter threw a non-connectivity error
CindelSyncConfig(
  adapter: AppSyncAdapter(),
  onStatusChanged: (status) {
    switch (status.phase) {
      case CindelSyncPhase.syncing:
        showSyncIndicator();
      case CindelSyncPhase.offline:
        showOfflineBanner();
      case CindelSyncPhase.error:
        reportError(status.lastError);
      case CindelSyncPhase.idle:
        hideSyncIndicator();
    }
  },
),

Durable outbox

Mutations are persisted in the same database as your app data before being pushed to the backend. If the app is closed or crashes before the adapter accepts a mutation, the outbox survives reopening the database and the scheduler will retry. The internal outbox storage is not part of the public API — it is managed entirely by Cindel.

Build docs developers (and LLMs) love