DataLocal for Supabase

Supabase synchronization and local materialized views powered by DataLocal.

This package keeps a deliberate boundary between Supabase as the remote source of truth and DataLocal as the fast local view. Installing it does not automatically synchronize every table.

Install

dependencies:
  datalocal: ^2.0.0
  datalocal_for_supabase: ^0.1.0
  supabase_flutter: ^2.16.0

Initialize Supabase normally, then connect a table to a DataLocal collection:

await Supabase.initialize(
  url: supabaseUrl,
  anonKey: supabaseAnonKey,
);

final database = await DataLocalDatabase.open(
  name: 'my_app',
  storage: DataLocalSharedPreferencesAsyncStorage(),
);
final localProducts = database.mapCollection('products');

final source = DataLocalSupabaseTableSource(
  client: Supabase.instance.client,
  table: 'products',
  primaryKey: 'id',
);
final sync = DataLocalSupabaseAdapter(
  localCollection: localProducts,
  source: source,
  primaryKey: 'id',
);

You may use DataLocalSqliteStorage() from datalocal_sqlite instead of the SharedPreferences adapter without changing the Supabase synchronization code.

Pull once

final report = await sync.pull();
print('Changed ${report.changed} cached rows');

final result = await localProducts
    .query()
    .where('active', isEqualTo: true)
    .orderBy('name')
    .get();

Pull upserts returned rows but does not delete cached rows absent from the response, because a custom source may be filtered or paginated.

Realtime materialized view

final reports = sync.watch().listen((report) {
  print('Realtime changed ${report.changed} rows');
});

final localSnapshots = localProducts.query().watch().listen((snapshot) {
  print('${snapshot.documents.length} products available locally');
});

await reports.cancel();
await sync.stop();
await localSnapshots.cancel();
await database.close();

The built-in table source emits complete table snapshots. Realtime reconciliation therefore removes local rows absent from a newer snapshot. For a custom source that emits partial snapshots, use:

sync.watch(deleteMissing: false);

Row identifiers and security

The configured primary-key value becomes the DataLocal document ID. Numeric and UUID keys are both converted to strings. Every row must contain a non-empty primary key.

Supabase Row Level Security remains the actual authorization boundary. A local cache is not an authorization system. Namespace or clear cached data during logout so one user cannot see another user's materialized view.

Current scope

This release implements remote-to-local pull and Supabase Realtime reconciliation. It intentionally does not claim an offline outbound write queue, retries, tombstones, or automatic conflict resolution. Continue using the Supabase client for remote writes until those semantics are implemented and documented explicitly.

Libraries

datalocal_for_supabase
Supabase synchronization and local materialized views for DataLocal.