Data Synchronization

The Synchronization Engine is responsible for continuously fetching data and updating the local database to stay synchronized with the backend, without missing a single event or encountering race conditions.

Server-Side Architecture (CQRS)

The Novyse backend employs a CQRS (Command Query Responsibility Segregation) pattern to manage data efficiently across distributed clients. This division creates two distinct layers:

  1. Materialized State (Snapshot): Primary tables (messages, chats, users) representing the current "truth".
  2. Event Pipeline (Mutations): Chronological logs used to synchronize offline clients without state conflicts.

Why we don't use Timestamps

Timestamps are vulnerable to clock skew and race conditions during high-volume periods. Novyse utilizes a deterministic Sequential Cursor Pattern based on auto-incrementing IDs.

The client simply requests "everything that happened after ID X", guaranteeing mathematical consistency: no skipped rows and no duplicated events, even through abrupt disconnects.

Synchronization Lifecycles

1. Initialization

When the app starts with an empty database, it performs a full bootstrap.

StepActionDescription
1RequestClient calls GET /user/initialize.
2SnapshotServer provides the full Materialized State (latest messages, active chats).
3BaselineServer returns the Maximum Current IDs for all entities to set local cursors.

2. Update

When the app wakes up or regains connectivity, it fetches only the delta. The server performs a membership diff to determine what the client has versus what it should have.

StepActionDescription
1Delta RequestClient sends its local chat and user lists (with cursors) via POST /user/update.
2Membership DiffServer queries the real memberships and compares them to what the client sent. Missing chats/users are flagged as new.
3New EntitiesFor each new chat/user, the server returns the full object (same structure as initialization) including all messages for new chats.
4Existing EventsFor chats/users the client already has, the server returns only the events that occurred after the cursor sent by the client.
5PipelineData is applied in order: New Chats → New Users → Messages → Chat Events → User Profile Events → Local User Events.

Response Structure

The update response splits chats and users into two sub-objects, while messages remains a flat array:

KeyContent
chats.newFull chat objects (members, pinned messages, eventID) for chats the client lacks
chats.eventsIncremental events for chats the client already has
users.newFull user profiles for users the client lacks
users.eventsIncremental profile events for users the client already has
messagesFlat array — all new messages from both new and existing chats
localPrivate user events (theme, muted chats, folders)

Network Transport: Ping & Fetch

LayerStateBehavior
Socket.IOForegroundReal-time updates. Payloads are processed instantly and trigger UI re-renders via Zustand.
Firebase FCMBackground"Silent Alarm". FCM wakes the app to execute a background Update Cycle before the user opens the notification.

The Event Pipeline

While the local database stores the Materialized State (the current snapshot), the server handles changes via an Event Pipeline. Instead of directly mutating the state on the server, we log alterations in event tables.

Crucially, these event logs never contain the creation of an entity (e.g., sending a new message), but only track its subsequent modifications over time to ensure eventual consistency across all devices.

Event Payload Structure

Every event delivered through the pipeline includes a payload object. This object contains a current field, which holds the exact value of the modification performed (e.g., the new message content, the new avatar UUID, or the updated theme preference). This ensures the client can apply the update directly without additional lookups.

1. Chat Events

These events manage the evolution of a conversation. They are synchronized only with users who are currently active members of the chat.

EventPayloadVisibility
MESSAGE_EDITED
{ messageID, content }
Chat Members Only
REACTION_ADDED
{ messageID, reaction }
Chat Members Only
REACTION_REMOVED
{ messageID, reaction }
Chat Members Only
MESSAGE_DELETED
{ messageID }
Chat Members Only
MESSAGE_PINNED
{ messageID }
Chat Members Only
MESSAGE_UNPINNED
{ messageID }
Chat Members Only
MEMBER_JOINED
{}
Chat Members Only
MEMBER_LEFT
{}
Chat Members Only

Optimized Message Mutations

To minimize network overhead and protect privacy, Novyse applies specific visibility rules for message history:

  • External Users: Only receive the most recent edit event for any given message, seeing only the "final" state since their last check.
  • Authors & Admins: Retain access to the Full Sequence of Changes, allowing them to audit the complete history of edits for that specific message.

2. User Profile Events

These events track changes to public-facing user data. They power the "Address Book" synchronization, ensuring contacts always see the latest profile data.

EventPayloadVisibility
BIO_CHANGED
{ biography }
Public
PICTURE_CHANGED
{ profilePictureUUID }
Public
BANNER_CHANGED
{ bannerPictureUUID }
Public
NAME_CHANGED
{ name }
Public
SURNAME_CHANGED
{ surname }
Public
BIRTHDAY_CHANGED
{ birthday }
Public
COLOR_CHANGED
{ color }
Public
HANDLE_CHANGED
{ handle }
Public

History Privacy & Visibility

To protect user privacy, Novyse applies a differential history policy for profile events:

  • External Users: When a contact fetches updates, they only receive the most recent event of each type. They cannot see the history of previous avatars or bios.
  • Account Owner: The owner of the account retains access to the Full Event Log across all their devices, ensuring their personal history and audit trail are preserved.

3. User Events

These events synchronize a user's private configuration and interaction metadata across all their personal devices.

EventPayloadVisibility
CHAT_PINNED
{ chatUUID, position }
Owner Only
CHAT_UNPINNED
{ chatUUID }
Owner Only

By combining ordered execution with cursor-based fetching, Novyse ensures that the local SQLite database is a perfect, eventually-consistent mirror of the server.

    Data Synchronization - Novyse Architecture | Novyse