SyncFrame
Sync protocol frame format for delta sync and real-time updates.
Import
import type { SyncFrame } from '@gluonic/client'Type Definition
Exact definition from implementation:
export type SyncFrame = {
sid: number
t: string
id: string
op: 'i' | 'u' | 'd'
p?: Record<string, any>
at?: number
ctx?: { client_tx_id?: string }
}Fields
sid
sid: numberDescription: Sync ID - monotonically increasing sequence number
Purpose: Track sync progress, enable delta sync
Example: 42, 1234
t
t: stringDescription: Model type name (e.g., ‘task’, ‘user’, ‘post’)
Example: 'task', 'user'
id
id: stringDescription: Unique model ID
Example: 'abc-123-def-456'
op
op: 'i' | 'u' | 'd'Description: Operation type
Values:
'i'- Insert (create)'u'- Update (modify)'d'- Delete (remove)
p
p?: Record<string, any>Description: Payload data (properties)
Optional: Present for 'i' and 'u', absent for 'd'
Example: { title: 'Updated Task', done: true }
at
at?: numberDescription: Timestamp (milliseconds since epoch)
Optional: Server-assigned operation time
Example: 1699564800000
ctx
ctx?: { client_tx_id?: string }Description: Context metadata
Fields:
client_tx_id- Client transaction ID (for optimistic update matching)
Example: { client_tx_id: 'tx-123-456' }
Complete Examples
Insert Frame
const insertFrame: SyncFrame = {
sid: 42,
t: 'task',
id: 'abc-123',
op: 'i',
p: {
title: 'New Task',
done: false,
createdAt: 1699564800000
},
at: 1699564800000,
ctx: { client_tx_id: 'tx-001' }
}Update Frame
const updateFrame: SyncFrame = {
sid: 43,
t: 'task',
id: 'abc-123',
op: 'u',
p: {
title: 'Updated Task',
done: true
},
at: 1699564900000
}Delete Frame
const deleteFrame: SyncFrame = {
sid: 44,
t: 'task',
id: 'abc-123',
op: 'd',
at: 1699565000000
}Usage
Delta Sync
Client requests frames since last sync ID:
// Client has lastSyncId = 40
// Request delta
GET /sync/v1/delta?since=40
// Server responds with frames 41, 42, 43...
{
sid: 43,
frames: [
{ sid: 41, t: 'task', id: '1', op: 'u', p: { done: true } },
{ sid: 42, t: 'task', id: '2', op: 'i', p: { title: 'New' } },
{ sid: 43, t: 'task', id: '3', op: 'd' }
]
}
// Client applies frames and updates lastSyncId to 43Real-Time Updates
Frames pushed via WebSocket:
// Server broadcasts frame
ws.send(JSON.stringify({
sid: 45,
t: 'task',
id: 'abc-123',
op: 'u',
p: { done: true }
}))
// Client receives and applies
const frame: SyncFrame = JSON.parse(data)
await store.applyFrames([frame])Optimistic Updates
Client mutations include client_tx_id:
// Client creates task
await store.create('task', 'abc-123', { title: 'New Task' })
// Queued with client_tx_id: 'tx-001'
// Server confirms via frame
{
sid: 50,
t: 'task',
id: 'abc-123',
op: 'i',
p: { title: 'New Task', serverTimestamp: 1699564800000 },
ctx: { client_tx_id: 'tx-001' }
}
// Client matches client_tx_id, skips duplicate applicationFrame Processing
Apply Frames
async applyFrames(frames: SyncFrame[]) {
for (const frame of frames) {
// Check if we originated this mutation
const isOurs = frame.ctx?.client_tx_id &&
this.pendingTxIds.has(frame.ctx.client_tx_id)
if (isOurs) {
// Skip (already applied optimistically)
// Just dequeue transaction
await storage.dequeueTx(frame.ctx.client_tx_id)
continue
}
// Apply frame to pool
switch (frame.op) {
case 'i':
case 'u':
pool.upsert({ t: frame.t, id: frame.id, p: frame.p })
break
case 'd':
pool.delete(frame.t, frame.id)
break
}
// Update lastSyncId
this.lastSyncId = Math.max(this.lastSyncId, frame.sid)
}
}Why SyncFrame?
1. Delta Sync Efficiency
Only transmit changes since last sync:
// Instead of full snapshot (large)
{ rows: [/* all rows */] }
// Send only changes (small)
{ frames: [
{ sid: 41, t: 'task', id: '1', op: 'u', p: { done: true } }
]}2. Operation Semantics
Clear intent for each change:
- Insert: New object
- Update: Modified fields
- Delete: Removed object
3. Optimistic Update Matching
client_tx_id enables deduplication:
// Client applies optimistically with tx-001
// Server echoes tx-001 in frame
// Client skips re-application (already done)4. Real-Time Streaming
WebSocket can stream frames incrementally:
// Frame 1
{ sid: 41, ... }
// Frame 2 (immediately after)
{ sid: 42, ... }Relationship to WireRow
SyncFrame is protocol format, WireRow is storage format:
// SyncFrame (from network)
{
sid: 42,
t: 'task',
id: 'abc-123',
op: 'i',
p: { title: 'New Task' }
}
// Convert to WireRow (for pool/storage)
{
t: 'task',
id: 'abc-123',
p: { title: 'New Task' }
}Differences:
- SyncFrame has
sid,op,at,ctx - WireRow has
v(version) - SyncFrame transmits changes
- WireRow stores state
See Also
- WireRow - Storage format
- Delta Sync Concept - How delta sync works
- Real-time Concept - WebSocket streaming
- Store - applyFrames() method