7.9 KiB
7.9 KiB
Offline-First Database Sync Plan
This document outlines the blueprint and instructions for linking the React Native mobile app with the Express-based web app server. It replaces direct-to-Supabase synchronization on the mobile client with web-server-mediated syncing.
1. Primary Intent
To redirect the React Native mobile app (nextgenmobile) sync path away from direct Supabase communication to a local-hub-mediated synchronization architecture using the Express-based web application server (server).
- Online State Definition: The system detects the mobile app is "online" if and only if the web server's API endpoint is reachable (running on port 3001).
- Offline State Definition: The system is "offline" when the web server is stopped, crashed, or network-unreachable.
- Offline-First Synchronization: The mobile app functions with full local write capability into its local SQLite database (using
expo-sqliteandPowerSync). Once a connection to the web server is verified, it pushes all locally accumulated changes (marked aspending) to the server via/api/sync/pushand pulls remote changes via/api/sync/pull.
2. Context Boundaries
In-Scope Files
- Mobile Client Sync Coordinator: sync.service.ts
- Mobile API Client Helper: api.ts
- Mobile DB Schema definitions: schemaSql.ts and database.ts
- Web App Server Sync Endpoints: sync.controller.js
- Web App Server Entry Point: index.js
- Web App Server DB Config/Sync Engine: SyncEngine.js
Synced Database Tables (23 Tables)
The synchronization covers the following entities:
users, departments, classes, subjects, courses, enrollments, attendance, fee_groups, student_fees, payments, messages, events, expenses, grades, assignments, submissions, staff_records, leave_requests, item_stock, items, hostels, rooms, routes
3. Reasoning Constraints
- Offline Writes: Writes are recorded in local SQLite immediately. No remote calls block user interaction.
- Change Tracking: Every table row uses the standard sync columns:
uid: Unique identifier (UUID string) shared globally.sync_status: Tracked as'pending'(unsynced),'synced'(up to date), or'conflict'.updated_at: Timestamp representing the last edit.is_deleted: Soft deletion flag (1= deleted,0= active).
- Server-Mediated Role-Based LWW: The local hub server resolves conflicts using Role-Based Last Write Wins (LWW) defined in sync.controller.js.
- Mobile-Side Pull Resolution: During a pull update, the client overwrites local database rows only if the incoming record's
updated_attimestamp is newer, or if it resolves a local'conflict'state.
4. Failure Behavior
- Server Unreachable / Connection Drops:
- The API call
probeBackend()(with a 3-second timeout) acts as the connection check before syncing. - If
probeBackend()returnsfalseor any fetch call throws a network error, the client gracefully cancels the sync cycle and transitions UI indicators to offline. Unsynced data must remain intact as'pending'.
- The API call
- Authentication Failures (401/403):
- If the server rejects the request with a token error, the sync engine suppresses fatal crashes. It marks the device as offline (or prompts re-login) and preserves the queue.
- Partial Table Failures:
- If syncing a particular table throws an SQL error, log it in the local
sync_logsand proceed to sync other tables. Increment the table's failure retry counts but do not block the rest of the queue.
- If syncing a particular table throws an SQL error, log it in the local
5. Output Context
Request Payload Contracts
- Push Endpoint (
POST /api/sync/push):{ "changes": { "messages": [ { "uid": "msg-uuid-123", "sender_id": 1, "body": "Hello", "updated_at": "2026-07-09T23:00:00Z" } ] } } - Pull Endpoint (
GET /api/sync/pull?last_sync=ISO_TIMESTAMP):{ "success": true, "changes": { "users": [...], "messages": [...] }, "server_time": "2026-07-09T23:15:00Z" }
6. Quality Bar
- SQLite Transactions: Push and pull updates per table must be wrapped in transactions (
db.transactionor SQLite batch execution) to prevent partial writes. - No Direct Cloud Writes: The client must not use
@supabase/supabase-jsfor data sync. All supabase communication goes through the server's backend sync engine. - Soft Deletion Preservation: Checking
is_deleted = 1updates must flow correctly to prevent deleted records from reappearing.
7. Step-by-Step Implementation Plan
Part 1: Backend Connection Probe & Network State
- Update api.ts to export
probeBackend(timeoutMs?: number)as the source of truth for the local server's online status. - In sync.service.ts, modify the
NetInfoevent listener. Instead of relying solely on the device's internet state, combine it with a periodic call toapi.probeBackend()to set theonlineflag.
Part 2: Mobilizing Push Sync to Web Server
- Remove references to
supabaseclient inside thepushChangesfunction in sync.service.ts. - Rewrite
pushChangesto scan all sync-enabled SQLite tables for rows wheresync_status = 'pending'. - Construct a single nested object of changes:
{ [tableName]: record[] }. - Send the payload to
/api/sync/pushusingapi.post('/sync/push', { changes }). - Upon receiving
{ success: true, results: { applied, rejected } }, update local database rows: setsync_status = 'synced'andlast_synced_at = nowIso().
Part 3: Mobilizing Pull Sync from Web Server
- Rewrite
pullChangesin sync.service.ts to replace Supabase calls withapi.get('/sync/pull', { last_sync: lastSyncTimestamp }). - Retrieve the changes object from the response.
- Iterate over each table and upsert records into local SQLite.
- Update local system settings or local storage variables with the returned
server_timeto use as the nextlast_synctimestamp.
Part 4: Conflict Resolution and LWW Implementation
- Ensure the SQLite pull execution in sync.service.ts compares timestamps if the local row is
pending. If the incoming server row is newer, update it; otherwise, flag the row asconflict. - Ensure the web server's sync.controller.js is correctly registering updates in
sync_metadatafor every table change.
Part 5: Local Database Transactional Consistency
- Wrap the pull logic updates in sync.service.ts using
powerSync.writeTransactionor transactional batch executes to ensure database state is never left half-synced if the app is closed mid-operation.
Part 6: Testing and Offline Simulation Checks
- Simulate offline state by stopping the webapp server (
npm stopor killing node process). - Perform inserts, updates, and deletes in the mobile app. Verify that changes are written locally and marked
pending. - Start the webapp server. Verify
probeBackendreturns true, triggers the auto-sync engine, and successfully synchronizes all queued changes to/api/sync/push. - Verify the web server's SyncEngine uploads the school hub data successfully to Supabase.