Skip to main content

Sync Component

Introduction​

The Sync component is a non-visual SDUI component that enables offline-first capabilities for mobile applications. It defines what data to pre-load and cache locally, how to queue operations (workflow executions, file uploads) when offline, and how to synchronize changes when connectivity is restored.

The Sync component acts as a data layer adapter β€” other SDUI components (datasource, form, button) interact with it transparently, without needing to know whether data comes from the network or local cache.

Key Capabilities​

  • Data pre-loading: Cache configurable datasets for offline access
  • Operation queuing: Persist workflow executions and mutations when offline
  • Background sync: Process queued operations automatically via expo-task-manager
  • Attachment uploads: Queue and upload files in the background with compression
  • Status visibility: Expose sync state for UI indicators and an operations screen
  • Conflict resolution: Last-write-wins strategy for offline changes

Design Principles​

  • Declarative: Sync behavior is defined in YAML, consistent with the SDUI pattern
  • Transparent: Consuming components don't distinguish between online and offline
  • Resilient: Queue persists across app restarts and survives app kills
  • Configurable: Each workflow and model can specify its own sync strategy

YAML Structure​

component: sync
name: <syncInstanceName>
props:
models:
- name: <string>
query: <graphql>
variables: <object>
keys: [<string>]
syncInterval: <number>
syncOn: [<trigger>]
maxAge: <number>

queue:
maxRetries: <number>
retryInterval: <number>
retryStrategy: <strategy>
persistQueue: <boolean>
backgroundSync: <boolean>
backgroundInterval: <number>
maxQueueSize: <number>

workflows:
default: <mode>
items:
- workflowId: <string>
mode: <mode>
optimisticResult: <object>
rollbackAction: <action[]>
dependsOn: [<string>]

attachments:
strategy: <strategy>
maxConcurrent: <number>
retryOnFailure: <boolean>
compress:
enabled: <boolean>
quality: <number>
maxDimension: <number>

status:
showIndicator: <boolean>
position: <position>
showPendingCount: <boolean>
notifyOnComplete: <boolean>
notifyOnError: <boolean>

Attribute Description​

Top-Level Props​

AttributeTypeRequiredDescription
modelsarrayNoList of data models to pre-load and cache for offline access
queueobjectNoConfiguration for the offline operation queue
workflowsobjectNoWorkflow execution mode configuration (queue vs optimistic)
attachmentsobjectNoFile upload queue configuration
statusobjectNoSync status UI indicator configuration

Models​

Each model defines a dataset to cache locally for offline access.

AttributeTypeDefaultDescription
namestringβ€”Required. Unique identifier for this model within the sync instance
querystringβ€”Required. GraphQL query string to fetch the data
variablesobject{}Template-parsed variables passed to the query. Supports {{ }} expressions
keysstring[]β€”Required. Primary key field(s) for cache identity and deduplication
syncIntervalnumber300Seconds between automatic background refreshes when online
syncOnstring[]["networkReconnect"]Additional triggers for syncing this model
maxAgenumber86400Maximum seconds before cached data is considered stale (default: 24h)
filterstringβ€”Optional default filter applied to cached results
maxItemsnumber1000Maximum number of items to cache for this model

syncOn Triggers​

ValueDescription
appForegroundSync when the app returns to the foreground
networkReconnectSync when network connectivity is restored
manualOnly sync when explicitly triggered by user action or refresh action
intervalOnlyOnly sync on the defined syncInterval, no event-based triggers

Queue​

Configuration for the offline operation queue that persists and processes mutations/workflows.

AttributeTypeDefaultDescription
maxRetriesnumber5Maximum retry attempts for a failed operation
retryIntervalnumber30Base retry delay in seconds
retryStrategystring"exponential"Backoff strategy: "exponential" or "fixed"
persistQueuebooleantrueWhether the queue persists to device storage (survives app restart)
backgroundSyncbooleantrueWhether to use expo-task-manager for background queue processing
backgroundIntervalnumber60Minimum seconds between background sync attempts
maxQueueSizenumber100Maximum pending operations before rejecting new entries

Workflows​

Defines how each workflow action behaves when executed offline.

AttributeTypeDefaultDescription
defaultstring"queue"Default mode for workflows not explicitly listed: "queue" or "optimistic"
itemsarray[]Per-workflow configuration overrides

Workflow Item​

AttributeTypeDefaultDescription
workflowIdstringβ€”Required. The workflow identifier to configure
modestringinheritedExecution mode: "queue" or "optimistic"
optimisticResultobjectβ€”Expected result shape applied locally when in optimistic mode
rollbackActionaction[]β€”Actions executed if the operation fails permanently after all retries
dependsOnstring[]β€”Queue item types that must complete before this workflow executes (e.g., ["upload"])

Execution Modes​

  • queue: The workflow call (workflowId + inputs) is stored in the queue. It executes server-side when connectivity is available. The user receives a "queued" confirmation but no immediate result.
  • optimistic: The optimisticResult is applied to local state immediately, giving the user instant feedback. When online, the actual workflow executes. If it fails permanently, rollbackAction is triggered and the optimistic state is reverted.

Attachments​

Configuration for the file upload queue.

AttributeTypeDefaultDescription
strategystring"background"Upload strategy: "background" (queue for later) or "immediate" (block until done)
maxConcurrentnumber2Maximum simultaneous upload operations
retryOnFailurebooleantrueWhether failed uploads are retried automatically
compress.enabledbooleanfalseWhether to compress images before upload
compress.qualitynumber0.8Image compression quality (0.0–1.0)
compress.maxDimensionnumber2048Maximum width or height in pixels (aspect ratio preserved)

Status​

Configuration for the sync status UI indicator.

AttributeTypeDefaultDescription
showIndicatorbooleantrueWhether to show a sync status badge/icon
positionstring"header"Where to display the indicator: "top", "bottom", or "header"
showPendingCountbooleantrueWhether to show the count of pending operations as a badge
notifyOnCompletebooleantrueShow notification when all queued operations sync successfully
notifyOnErrorbooleantrueShow notification when an operation fails permanently

Adapter Interface​

Other SDUI components interact with the Sync component via the syncAdapter prop. This provides transparent offline access without the consuming component needing to handle online/offline logic.

Usage in DataSource​

- component: datasource
name: vehicleData
props:
syncAdapter: receivingSync
model: vehicles
filter: "trackingNumber:{{ trackingNumber }}"

When syncAdapter is specified, the datasource reads from the local cache first. If online and data is stale, it fetches fresh data in the background.

Usage in Workflow Actions​

- component: button
props:
label:
en-US: Submit
onClick:
- workflow:
workflowId: "6bda41d8-..."
inputs:
data: "{{ form.values }}"
syncAdapter: receivingSync
onQueued:
- notification:
message:
en-US: "Saved offline. Will sync when connected."
type: info
onSynced:
- notification:
message:
en-US: "Successfully synced to server."
type: success

Adapter Actions​

The sync adapter exposes the following actions that can be triggered from YAML:

ActionDescription
syncNowForce immediate sync of all models and process queue
syncModelForce sync of a specific model by name
clearCacheClear all cached data for this sync instance
clearQueueDiscard all pending queue operations
retryFailedRetry all permanently failed operations
- component: button
props:
label:
en-US: Refresh Data
onClick:
- syncAction:
adapter: receivingSync
action: syncModel
model: vehicles

New Action Events​

When syncAdapter is specified on a workflow action, additional events become available:

EventDescription
onQueuedFired when the operation is added to the offline queue (offline path)
onSyncedFired when a previously queued operation completes successfully
onSyncFailedFired when a queued operation fails permanently after all retries

Examples​

Basic: Cache a Single Model​

Pre-load customer contacts for offline access with refresh every 10 minutes:

component: sync
name: contactsSync
props:
models:
- name: customers
query: |
query($organizationId: Int!) {
contacts(organizationId: $organizationId, filter: "contactType:Customer") {
items {
contactId
name
email
phone
}
}
}
variables:
organizationId: "{{ number organizationId }}"
keys: [contactId]
syncInterval: 600
syncOn: [appForeground, networkReconnect]

Intermediate: Multiple Models with Workflow Queue​

A receiving module that caches vehicles and locations, with offline workflow support:

component: sync
name: receivingSync
props:
models:
- name: vehicles
query: |
query($organizationId: Int!, $filter: String) {
orders(organizationId: $organizationId, filter: $filter, limit: 500) {
items {
orderId
trackingNumber
orderStatus { orderStatusName }
orderCommodities {
commodity { customValues }
}
billToContact { contactId name }
}
}
}
variables:
organizationId: "{{ number organizationId }}"
filter: "orderType:Freight"
keys: [orderId]
syncInterval: 300
syncOn: [appForeground, networkReconnect]

- name: warehouseLocations
query: |
query($organizationId: Int!) {
warehouseLocations(organizationId: $organizationId) {
items {
warehouseLocationId
name
}
}
}
variables:
organizationId: "{{ number organizationId }}"
keys: [warehouseLocationId]
syncInterval: 3600

queue:
maxRetries: 5
retryStrategy: exponential
backgroundSync: true
backgroundInterval: 60

workflows:
default: queue
items:
- workflowId: "6bda41d8-28af-42ac-9975-0f7adc0dc45e"
mode: optimistic
optimisticResult:
status: completed
rollbackAction:
- notification:
message:
en-US: "Receiving failed to sync. Check Sync Operations."
type: error
dependsOn: [upload]

- workflowId: "2ff3c10b-d2d9-418f-a41a-8834a58abf2e"
mode: queue

Advanced: Full Offline Module with Attachments​

Complete sync configuration for a field inspection module with photo capture and background upload:

component: sync
name: inspectionSync
props:
models:
- name: assignments
query: |
query($organizationId: Int!, $assignedTo: String!) {
inspectionAssignments(
organizationId: $organizationId
filter: "assignedTo:{{ assignedTo }} AND status:Pending"
) {
items {
assignmentId
location { name address }
scheduledDate
inspectionType
notes
}
}
}
variables:
organizationId: "{{ number organizationId }}"
assignedTo: "{{ user.userId }}"
keys: [assignmentId]
syncInterval: 300
syncOn: [appForeground, networkReconnect]
maxItems: 200

- name: inspectionTemplates
query: |
query($organizationId: Int!) {
inspectionTemplates(organizationId: $organizationId) {
items {
templateId
name
sections { sectionId title fields { fieldId label type required } }
}
}
}
variables:
organizationId: "{{ number organizationId }}"
keys: [templateId]
syncInterval: 86400
syncOn: [manual]

queue:
maxRetries: 5
retryStrategy: exponential
retryInterval: 30
backgroundSync: true
backgroundInterval: 60
maxQueueSize: 200
persistQueue: true

workflows:
default: optimistic
items:
- workflowId: "a1b2c3d4-complete-inspection"
mode: optimistic
optimisticResult:
status: Completed
completedAt: "{{ format now() ISO }}"
rollbackAction:
- notification:
message:
en-US: "Inspection submission failed. Your data is saved locally."
type: warning
dependsOn: [upload]

attachments:
strategy: background
maxConcurrent: 2
retryOnFailure: true
compress:
enabled: true
quality: 0.8
maxDimension: 2048

status:
showIndicator: true
position: header
showPendingCount: true
notifyOnComplete: true
notifyOnError: true

Using the Adapter in a Form​

How other components consume the sync adapter for seamless offline operation:

- component: form
name: inspectionForm
props:
validationSchema: {}
children:
# Load assignment data from cache
- component: datasource
name: assignmentData
props:
syncAdapter: inspectionSync
model: assignments
filter: "assignmentId:{{ assignmentId }}"

# Form fields...
- component: field
name: notes
props:
type: text
label:
en-US: Inspection Notes

# Photo attachment with offline queue
- component: field
name: photos
props:
type: attachment
label:
en-US: Take Photos
options:
allowMultiple: true
displayAs: image
parentType: Inspection
parentId: "{{ assignmentId }}"

# Submit with offline support
- component: button
name: submitInspection
props:
label:
en-US: Complete Inspection
options:
variant: primary
onClick:
- validateForm:
validationSchema:
notes:
required:
message: Notes are required
- workflow:
workflowId: "a1b2c3d4-complete-inspection"
inputs:
assignmentId: "{{ assignmentId }}"
notes: "{{ inspectionForm.notes }}"
photos: "{{ inspectionForm.photos }}"
syncAdapter: inspectionSync
onQueued:
- notification:
message:
en-US: "Inspection saved. Will sync when online."
type: info
- navigateBack:
onSynced:
- notification:
message:
en-US: "Inspection synced successfully."
type: success

Global Sync (System Module)​

A system-level sync component that caches shared data used across all modules:

component: sync
name: globalSync
props:
models:
- name: contacts
query: |
query($organizationId: Int!) {
contacts(organizationId: $organizationId, limit: 1000) {
items { contactId name contactType email phone }
}
}
variables:
organizationId: "{{ number organizationId }}"
keys: [contactId]
syncInterval: 600
syncOn: [appForeground, networkReconnect]

- name: warehouseLocations
query: |
query($organizationId: Int!) {
warehouseLocations(organizationId: $organizationId) {
items { warehouseLocationId name code }
}
}
variables:
organizationId: "{{ number organizationId }}"
keys: [warehouseLocationId]
syncInterval: 3600

- name: users
query: |
query($organizationId: Int!) {
users(organizationId: $organizationId) {
items { userId displayName email }
}
}
variables:
organizationId: "{{ number organizationId }}"
keys: [userId]
syncInterval: 3600
syncOn: [appForeground]

status:
showIndicator: true
position: header
showPendingCount: false

Sync Operations Screen​

The Sync component automatically registers a system screen at the route sync-operations that displays all sync instances, their cached models, and the operation queue.

Screen Sections​

  1. Status Bar: Current network state (Online/Offline), last sync timestamp, manual Sync Now button
  2. Sync Instances: Grouped by sync name, showing:
    • Each cached model with item count and freshness indicator
    • Pending queue items with operation type, timestamp, and retry count
  3. Queue Actions: Per-item Retry and Discard buttons
  4. History: Completed and failed operations with timestamps

Accessing the Screen​

- component: button
props:
label:
en-US: View Sync Status
onClick:
- navigate: "sync-operations"

The screen is also accessible via the sync status indicator when tapped.

Architecture​

Data Flow​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ YAML Sync Definition β”‚
β”‚ (models, queue, workflows, attachments) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ parsed at module load
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Sync Engine β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚Model Store β”‚ β”‚ Operation Queue β”‚ β”‚
β”‚ β”‚(TanStack Q)β”‚ β”‚ (Zustand+persist)β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Scheduler β”‚ β”‚Background Worker β”‚ β”‚
β”‚ β”‚(refetch) β”‚ β”‚(expo-task-mgr) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Sync Adapter Interface β”‚
β”‚ read() | queue() | upload() | status() β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ consumed by
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SDUI Components β”‚
β”‚ datasource | form | button | dataGrid β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Storage Strategy​

Data TypeStoragePersistence
Cached model dataTanStack Query + AsyncStorage persisterSurvives app restart
Operation queueZustand store + AsyncStorageSurvives app restart
Attachment queueZustand store + file systemSurvives app restart
Sync configurationIn-memory (parsed from YAML)Re-parsed on module load
Network stateIn-memory (NetInfo)Real-time only

Queue Processing Order​

When connectivity is restored, the background worker processes operations in this order:

  1. Attachment uploads (to obtain real attachmentId values)
  2. Workflows with dependsOn: [upload] (use real attachment IDs)
  3. Independent workflows (no dependencies, processed in FIFO order)
  4. Model refresh (pull latest data after mutations complete)

Best Practices​

  • Scope sync instances narrowly: Define per-module sync with only the data that module needs, rather than caching everything globally. This reduces storage usage and sync time.
  • Use dependsOn for attachment workflows: Always declare dependsOn: [upload] for workflows that reference uploaded file IDs. This prevents submitting workflows with placeholder attachment references.
  • Set appropriate syncInterval: Frequently changing data (vehicles, orders) benefits from shorter intervals (300s). Reference data (locations, templates) can use longer intervals (3600s+).
  • Prefer queue mode for data-creation workflows: Use optimistic mode only when you can reliably predict the server result. For complex workflows with server-side validation, queue mode with clear "saved offline" messaging is safer.
  • Configure maxItems for large datasets: Prevent excessive storage usage by limiting cached items to what a field worker reasonably needs in a session.
  • Use syncOn: [manual] for large reference data: Inspection templates or configuration data that rarely changes should only sync on demand to avoid unnecessary network traffic.
  • Handle onSyncFailed gracefully: Provide clear user messaging and a path to the Sync Operations screen where they can retry or discard failed operations.
  • Test with airplane mode: Validate the complete offline flow by enabling airplane mode, performing all operations, then re-enabling connectivity to verify queue processing.