How It Works
Understanding Talon's message-based sync architecture
How It Works
Talon uses a message-based sync architecture. Every change is stored as a message that flows through a simple pipeline.
The Big Picture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Your App │────▶│ Talon │────▶│ Server │
│ │ │ │ │ │
│ saveChange()│ │ Messages │ │ Messages │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Local DB │
│ │
│ Messages │
│ + Data │
└─────────────┘
1. Local Change
When you call saveChange():
await talon.saveChange(
table: 'todos',
row: 'todo-1',
column: 'name',
value: 'Buy milk',
);
Talon:
- Creates a
Messagewith an HLC timestamp - Saves the message to the local messages table
- Applies the change to your data table
- Emits a
TalonChangeevent - Queues the message for sync
2. Sync to Server
When sync runs:
// Automatic when syncIsEnabled = true
// Or manual: await talon.forceSyncToServer();
Talon:
- Gets all unsynced messages (
hasBeenSynced = false) - Sends them to the server in batches
- Marks successfully sent messages as synced
3. Sync from Server
// Automatic via subscription
// Or manual: await talon.runSync();
Talon:
- Fetches messages from server (after last sync timestamp)
- Filters out messages from this client
- For each message:
- Saves to local messages table
- Checks HLC timestamp against existing value
- Applies if newer (conflict resolution)
- Emits
TalonChangeevents
Data Flow Diagram
┌──────────────────────────────────────────────────────────────┐
│ YOUR APP │
│ │
│ saveChange() ──┐ ┌── changes.listen() │
│ │ │ │
└─────────────────┼──────────────────────┼─────────────────────┘
│ │
▼ │
┌──────────────────────────────────────────────────────────────┐
│ TALON │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Create │ │ Apply to │ │ Emit │ │
│ │ Message │───▶│ Local DB │───▶│ Change │ │
│ │ + HLC │ │ │ │ Event │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ │ ┌─────────────┐ │
│ └──────────▶│ Queue for │ │
│ │ Sync │ │
│ └─────────────┘ │
│ │ │
└────────────────────────────┼─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ SERVER │
│ │
│ messages table (shared across all clients) │
│ │
└──────────────────────────────────────────────────────────────┘
Messages Table
Stores all messages (changes) for sync tracking:
CREATE TABLE talon_messages (
id TEXT PRIMARY KEY,
table_name TEXT,
row TEXT,
"column" TEXT,
value TEXT,
data_type TEXT,
local_timestamp TEXT, -- HLC for conflict resolution
server_timestamp INTEGER, -- For incremental sync
user_id TEXT,
client_id TEXT,
hasBeenSynced INTEGER, -- 0 = needs sync, 1 = synced
hasBeenApplied INTEGER -- 0 = pending, 1 = applied
);
Your Data Tables
Your actual data (unchanged by Talon):
CREATE TABLE todos (
id TEXT PRIMARY KEY,
name TEXT,
is_done INTEGER,
created_at TEXT
);
Sync States
A message goes through these states:
| State | hasBeenSynced | hasBeenApplied | Description |
|---|---|---|---|
| Created | false | true | Local change, applied, awaiting sync |
| Synced | true | true | Sent to server successfully |
| Received | true | true | From server, applied locally |
| Received (conflict) | true | false | From server, not applied (older) |
Real-Time Updates
Talon supports real-time updates via subscriptions:
// Talon subscribes to server messages
talon.syncIsEnabled = true; // This triggers subscription
// Your app listens to Talon's stream
talon.serverChanges.listen((change) {
// New data from another device!
if (change.affectsTable('todos')) {
refreshTodoList();
}
});
Next Steps
- Messages - Deep dive into the Message model
- Conflict Resolution - How HLC works
- Sync Lifecycle - Detailed sync process