OfflineOutbox class

Append-only event log of pending Supabase writes that the device couldn't (or won't) flush online immediately.

Each enqueued row represents one logical write intent — e.g. "insert this conversation_messages row" or "upsert this user_topic_progress row". The SyncWorker drains the log when connectivity returns, replaying each event against the live Supabase client.

The schema is deliberately generic (a single outbox_events table with a payload JSON blob) rather than mirroring every Supabase table. Reasons:

  1. Phase 3 only needs the write path to survive offline; reads remain online-first (the chat notifier keeps messages in memory during a session). Denormalized per-table mirrors land later if and when read-path offline support is required.
  2. Adding a new table to the outbox means a new tableName/payload shape, not a schema migration on the local DB.
  3. The original plan used Drift; the current riverpod_lint pin made its analyzer-10 requirement incompatible, so this is the raw sqlite3 equivalent. See pubspec.yaml for the resolution history.

Web has no native sqlite — local_vector_store_stub.dart set the precedent; the matching offline_outbox_stub.dart returns no-op instances and the Riverpod provider yields null under kIsWeb.

Constructors

OfflineOutbox()

Properties

hashCode int
The hash code for this object.
no setterinherited
isReady bool
no setter
runtimeType Type
A representation of the runtime type of the object.
no setterinherited

Methods

delete(int id) → void
Permanently remove an event. Used by the "Needs attention" UI when the user explicitly discards a row, and for periodic cleanup of synced rows older than a retention window (not yet implemented).
dispose() → void
enqueue({required String localId, required String tableName, required String operation, required Map<String, dynamic> payload, DateTime? clientUpdatedAt}) Future<int>
Append a write intent. Returns the autoincrement row id. clientUpdatedAt is the wall-clock at the time of the user action, used by SyncWorker for last-write-wins conflict resolution.
failedEvents({int limit = 50}) List<OutboxEvent>
Just the failed rows — convenience for the "Needs attention" list, which doesn't want to show pending/syncing rows because those resolve on their own.
initialize() Future<void>
markFailed(int id, String error) → void
Mark an event as failed; SyncWorker will retry with backoff next cycle. After enough attempts the row stays in failed and is surfaced in the "Needs attention" UI.
markPending(int id) → void
Flip a failed row back to pending so the next SyncWorker drain attempts it again. Used by the "Needs attention" UI's retry button. Resets last_error so a stale message doesn't keep showing while the row is back in flight.
markSynced(int id, {String? serverId}) → void
Mark an event as successfully replayed. Records the server-assigned id (if any) so future re-emits (e.g. an update after an insert) can reference the right row.
markSyncing(int id) → void
Mark an event as in-flight to a Supabase write. Prevents the SyncWorker from picking it up again if it crashes mid-call.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
pendingAndFailed({int limit = 100}) List<OutboxEvent>
Oldest-first list of events that haven't synced yet. failed rows are surfaced too so the SyncWorker can retry them (with backoff) and the "Needs attention" UI can show them.
pendingCount() int
Count of unsynced rows. Used by Settings to surface a "N pending" hint and by SyncWorker to short-circuit when empty.
toString() String
A string representation of this object.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited