supabase_flutter_ultra πŸš€

"Supabase with Offline Superpowers"

A powerful offline-first wrapper for supabase_flutter that adds intelligent caching, an offline write queue, delta sync, and multi-strategy conflict resolution β€” with zero breaking changes to existing Supabase APIs.


The Problem

supabase_flutter (573K downloads/week) has one critical gap: no offline support.

Without Ultra With Ultra
insert throws PostgrestException when offline Write queued, replayed on reconnect
select returns null/exception when offline Returns cached data instantly
Realtime drops events during offline periods Events buffered, replayed on reconnect
You rebuild SQLite + Queue + Retry in every project One package, zero boilerplate

Quick Start

// main.dart
await SupabaseUltra.initialize(
  url: 'https://your-project.supabase.co',
  anonKey: 'your-anon-key',
  config: UltraConfig(
    defaultCachePolicy: CachePolicy(
      maxAge: Duration(hours: 2),
      strategy: CacheStrategy.cacheFirstThenNetwork,
    ),
    defaultConflictPolicy: ConflictPolicy.lastWriteWins,
    autoSyncOnReconnect: true,
  ),
);

// Use anywhere
final ultra = SupabaseUltra.instance.client;

// READ β€” returns cache instantly, refreshes in background
final todos = await ultra.ultra('todos')
    .select('id, title, done')
    .eq('done', false)
    .order('created_at')
    .limit(50)
    .fetch();

// WRITE β€” works online and offline
await ultra.ultra('todos').insert({'title': 'Buy milk', 'done': false});
await ultra.ultra('todos').update({'done': true}, matchColumn: 'id', matchValue: '123');
await ultra.ultra('todos').delete(id: '123');

// Bypass cache/queue (raw Supabase)
final user = ultra.raw.auth.currentUser;

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  UltraClient                     β”‚  ← Your app talks to this
β”‚              (Facade / Composition)              β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚          β”‚          β”‚          β”‚
  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
  β”‚  Cache  β”‚ β”‚ Queue β”‚ β”‚ Sync  β”‚ β”‚ Realtime β”‚
  β”‚ Manager β”‚ β”‚  Ops  β”‚ β”‚Engine β”‚ β”‚  Buffer  β”‚
  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚          β”‚          β”‚          β”‚
  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
  β”‚           SupabaseClient (original)          β”‚  ← Untouched
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Features

πŸ—„οΈ Intelligent Cache

  • 5 strategies: networkFirstWithFallback, cacheFirstThenNetwork, cacheIfFreshElseNetwork, cacheOnly, networkOnly
  • LRU eviction + TTL expiry β€” configurable per table
  • Optimistic updates β€” UI reflects writes instantly before server confirmation
  • SQLite backend (mobile/desktop) + Memory backend (web/tests)

πŸ“€ Offline Write Queue

  • Every insert, update, delete while offline is persisted to SQLite and survives app restarts
  • FIFO ordering β€” operations replay in exact enqueue order
  • Exponential back-off retry with configurable max attempts
  • Dead-letter for permanently failed operations

πŸ”„ Delta Sync

  • Uses updated_at > lastSyncAt filters β€” no full-table downloads
  • Per-table sync checkpoints stored in SyncManifest
  • Periodic background sync (configurable interval)
  • Auto-sync on reconnect (configurable debounce delay)

βš”οΈ Conflict Resolution (5 Strategies)

Strategy Use Case
serverWins Server is authoritative (computed fields)
clientWins Client is authoritative (personal notes)
lastWriteWins Compares updated_at timestamps
fieldLevelMerge Three-way merge, field by field
custom Your own ConflictHandler function

πŸ“‘ Offline-Aware Realtime

  • Events received while offline are buffered (configurable TTL + capacity)
  • On reconnect, buffered events are replayed to listeners in order
  • Auto-resubscribe after reconnect

Configuration

UltraConfig(
  // Global cache policy
  defaultCachePolicy: CachePolicy(
    maxAge: Duration(hours: 1),
    maxEntries: 500,
    strategy: CacheStrategy.networkFirstWithFallback,
  ),

  // Global conflict policy
  defaultConflictPolicy: ConflictPolicy.lastWriteWins,

  // Queue limits
  maxQueueSize: 1000,
  maxRetryAttempts: 10,
  baseRetryDelay: Duration(seconds: 5),
  maxRetryDelay: Duration(minutes: 5),

  // Sync behavior
  autoSyncOnReconnect: true,
  reconnectSyncDelay: Duration(seconds: 2),
  periodicSyncInterval: Duration(minutes: 15),

  // Per-table overrides
  tableConfigs: {
    'todos': TableSyncConfig(
      cachePolicy: CachePolicy.aggressive,
      conflictPolicy: ConflictPolicy.fieldLevelMerge,
      primaryKey: 'id',
      updatedAtField: 'updated_at',
      supportsSoftDelete: false,
    ),
    'messages': TableSyncConfig(
      cachePolicy: CachePolicy.conservative,
      conflictPolicy: ConflictPolicy.custom,
      customConflictHandler: (local, server) async {
        // Your custom logic here
        return server; // example: server wins
      },
    ),
  },
)

Reactive UI Integration

// Sync status indicator
StreamBuilder<SyncState>(
  stream: ultra.syncStateStream,
  builder: (context, snap) {
    final state = snap.data!;
    return Text(state.status.name); // idle, syncing, completed, error
  },
);

// Pending operations badge
StreamBuilder<int>(
  stream: ultra.pendingOperationsCount,
  builder: (context, snap) => Text('${snap.data} pending'),
);

// Network status
StreamBuilder<NetworkState>(
  stream: ultra.networkStateStream,
  builder: (context, snap) {
    final online = snap.data?.isOnline ?? true;
    return Icon(online ? Icons.wifi : Icons.wifi_off);
  },
);

Add these columns to your Supabase tables for full delta sync:

ALTER TABLE todos
  ADD COLUMN updated_at TIMESTAMPTZ DEFAULT now(),
  ADD COLUMN created_at TIMESTAMPTZ DEFAULT now();

-- Auto-update updated_at on every row change
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER todos_updated_at
  BEFORE UPDATE ON todos
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

File Structure

supabase_flutter_ultra/
β”œβ”€β”€ lib/src/
β”‚   β”œβ”€β”€ core/          # UltraClient, UltraConfig, SupabaseUltra initializer
β”‚   β”œβ”€β”€ cache/         # CacheManager, policies, SQLite/Memory adapters, LRU/TTL eviction
β”‚   β”œβ”€β”€ queue/         # OperationQueue, QueueProcessor, RetryStrategy, QueuePersistence
β”‚   β”œβ”€β”€ sync/          # SyncEngine, DeltaSync, SyncScheduler, SyncManifest
β”‚   β”œβ”€β”€ conflict/      # ConflictResolver + 5 strategy implementations + VectorClock
β”‚   β”œβ”€β”€ network/       # ConnectivityMonitor, NetworkState, ReachabilityChecker
β”‚   β”œβ”€β”€ realtime/      # UltraRealtime, EventBuffer, RealtimeReconciler
β”‚   β”œβ”€β”€ query/         # UltraQueryBuilder, UltraFilterBuilder, QueryInterceptor
β”‚   β”œβ”€β”€ models/        # SyncMetadata, LocalRecord, ConflictRecord
β”‚   └── utils/         # IdGenerator, TimestampUtils, SerializationHelper
β”œβ”€β”€ test/
β”‚   β”œβ”€β”€ unit/          # Cache, queue, conflict unit tests
β”‚   └── integration/   # Offline scenario tests (no live network required)
└── example/           # Full offline-first Todo app

License

MIT β€” see LICENSE

Libraries

supabase_flutter_ultra
supabase_flutter_ultra β€” Offline-First Superpowers for Supabase