Architecture Overview
RXDB
(IndexDB /
SQL Lite)
GRAPHQL API
POSTGRESQL
(relational data) / DynamoDB + S3 (documents & blobs only)
The architecture plan ensures:
- Unified offline persistence
- Efficient sync cycles
- Server-side merge/conflict logic
- Enterprise reliability
Frontend Architecture (Capacitor + RxDB)
The plan is to convert the Vessel Vanguard application to a single codebase using:
- React – NextJS
- Capacitor runtime for mobile
- Browser deployment for web
Benefits: Capacitor
- One shared UI
- One shared business logic layer
- One shared sync/persistence implementation
- Native APIs (camera, file access, etc.) via Capacitor plugins
RxDB will provide a local database layer for handling offline in tablet and mobile devices:
- Observable document patterns
- Built-in conflict handling
- IndexedDB for web
- SQLite through Capacitor
- SQLite plugin for mobile
Responsibilities:
- Store all relational data locally
- Manage pending offline mutations (“outbox”)
- Merge server deltas
- Provide instant UI access to data
Importantly the local schema in the devices must match each table in Postgres corresponds to an RxDB collection. For example:
- vessels
- tasks
- documents (incl. metadata)
- checklists
- users
- roles
- relationships (vessel → task → document)
Each collection includes:
- id
- domain attributes
- updated_at
- _deleted flag (soft deletes)
Backend Architecture
A Postgres database becomes the primary data store for all structured, relational, transactional data.
Reasons for Postgres:
- ACID transactions
- Strong referential integrity
- Complex joins
- Efficient indexing
- Full-text search (if needed)
- Easy to run differential queries (for sync)
Large binary objects (PDFs, manuals, certificates) remain in the current DynamoDB/S3 architecture. Metadata is moved to Postgres.
A custom GraphQL server (Apollo Server) sits between clients and Postgres. It exposes:
- Standard CRUD Resolvers – Used for normal online operations.
- Delta Sync Endpoints
Clients request:
GET /sync?since=timestamp
The server returns all rows where
updated_at > last_sync
Across all tables relevant to the user.
- Push Endpoint (mutations batch)
Clients send queued offline changes:
POST /push
{
changes: […],
client_timestamp,
}
Server applies conflict rules then returns authoritative records
Sync Model
The sync model consists of
- Local-first reads
- Pull-based delta sync
- Client-side outbox (pending mutations)
- Server-side merge/conflict resolution
Pull Cycle (Download)
- Client stores last_sync_timestamp locally.
- Request:
-
/sync?since={timestamp}
- Server fetches all items updated after that.
- Client merges them into RxDB.
Push cycle (Upload)
- User edits local records.
- Changes are added to pending_changes outbox.
- When online, client pushes all pending changes.
- Server applies updates and resolves conflicts.
Conflict Resolution
Handled on the server – Common strategies:
- Last write wins (simple)
- Server authoritative
- Field-level merge (if both client and server changed different attributes)
- Domain-specific rules (tasks, deadlines, etc.)
Performance Considerations
- Data transfer Reduction Syncing only updated rows instead of entire tables.
- Local Query Speed SQLite and IndexedDB guarantee millisecond reads
- Index Queries All Postgres tables require:
- Primary index on id
- Secondary index on updated_at
Security & Permission Considerations
Authentication
- Cognito, Auth0, or custom JWT
- Access tokens include user role + vessel permissions
Authorization
- Role-based
- Vessel-based
- Field-level protections for tasks, docs, checklists
Offline Security
- Encrypt SQLite at rest (device)
- Encrypt IndexedDB using CryptoKey
- Ensure local role-based filtering persists offline