Store
Core sync engine store with mutation and sync methods.
Import
import { useStore } from '@gluonic/client'Class Signature
class Store {
// Properties
pool: ObjectPool
lastSyncId: number
networkEnabled: boolean
// Observable state
bootstrapping: boolean
fastForwarding: boolean
replayingQueue: boolean
initializingSession: boolean
// Computed
get catchingUp(): boolean
get isReplayingQueue(): boolean
get isInitializingSession(): boolean
// Methods
isHydrating(type: string): boolean
hasHydrated(type: string): boolean
// Lifecycle
async init(): Promise<void>
async bootstrap(): Promise<void>
async fastForward(): Promise<void>
// Mutations
async create(t: string, id: string, patch: Record<string, any>, clientTxId?: string): Promise<void>
async save(t: string, id: string, patch: Record<string, any>, clientTxId?: string): Promise<void>
async remove(t: string, id: string, clientTxId?: string): Promise<void>
// Hydration
async hydrateType(t: string): Promise<void>
async ensureRowsByIds(t: string, ids: string[]): Promise<void>
}Properties
pool
pool: ObjectPoolAccess to the ObjectPool (flat MobX map of WireRows).
Usage: Advanced scenarios only. Use hooks for normal access.
const row = store.pool.get('task', taskId)lastSyncId
lastSyncId: numberLatest sync ID received from server. Used for delta sync.
networkEnabled
networkEnabled: booleanControl network connectivity. Set to false to pause sync.
// Go offline
store.networkEnabled = false
// Go online
store.networkEnabled = true
await store.fastForward() // Catch upObservable State
bootstrapping
bootstrapping: booleantrue when running initial bootstrap from server.
fastForwarding
fastForwarding: booleantrue when running delta sync (catching up with server).
replayingQueue
replayingQueue: booleantrue when replaying pending transaction queue.
initializingSession
initializingSession: booleantrue when initializing a new session (covers entire bootstrap lifecycle).
catchingUp
get catchingUp(): booleanComputed property: true if bootstrapping or fastForwarding.
Usage:
const SyncIndicator = observer(() => {
const store = useStore()
if (store.catchingUp) {
return <Spinner>Syncing...</Spinner>
}
return null
})isReplayingQueue
get isReplayingQueue(): booleanAlias for replayingQueue (getter for consistency).
isInitializingSession
get isInitializingSession(): booleanAlias for initializingSession (getter for consistency).
Type Hydration Status
isHydrating()
isHydrating(type: string): booleanCheck if a specific type is currently hydrating from storage.
Parameters:
| Parameter | Type | Description |
|---|---|---|
type | string | Model type name |
Returns: boolean - true if currently hydrating
Example:
if (store.isHydrating('task')) {
return <Spinner>Loading tasks...</Spinner>
}hasHydrated()
hasHydrated(type: string): booleanCheck if a specific type has completed hydration from storage.
Parameters:
| Parameter | Type | Description |
|---|---|---|
type | string | Model type name |
Returns: boolean - true if hydration complete
Example:
if (!store.hasHydrated('task')) {
await store.hydrateType('task')
}Lifecycle Methods
init()
async init(): Promise<void>Initialize the store. Loads last sync ID and pending transactions from storage.
Behavior:
- Reads
lastSyncIdfrom storage - Loads pending transaction queue into memory cache
- Must be called once on app start before using store
Example:
import { client } from './sync'
// Call once on app start
useEffect(() => {
client.store.init()
}, [])bootstrap()
async bootstrap(): Promise<void>Load all data from server (initial sync).
Behavior:
- Only runs if
lastSyncId <= 0(first sync) - Fetches complete snapshot from server
- Applies rows to pool and storage
- Updates
lastSyncId - Sets
bootstrappingobservable during operation
Example:
// Usually called automatically by SyncProvider
// Can be called manually:
await store.bootstrap()fastForward()
async fastForward(): Promise<void>Delta sync to catch up with latest server changes.
Behavior:
- Fetches only changes since
lastSyncId - Applies delta frames to pool
- Updates
lastSyncId - Sets
fastForwardingobservable during operation
Example:
const SyncButton = observer(() => {
const store = useStore()
const handleSync = async () => {
await store.fastForward()
}
return <button onClick={handleSync}>Sync Now</button>
})Mutation Methods
create()
async create(
t: string,
id: string,
patch: Record<string, any>,
clientTxId?: string
): Promise<void>Create a new model.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
t | string | Yes | Model type name |
id | string | Yes | Model ID (usually UUID) |
patch | Record<string, any> | Yes | Initial data |
clientTxId | string | No | Custom transaction ID |
Behavior:
- Applies optimistically to pool (not persisted yet)
- Queues transaction for server
- Returns immediately (optimistic)
- Server confirms in background
Example:
const store = useStore()
const id = crypto.randomUUID()
await store.create('task', id, {
title: 'New Task',
done: false,
assigneeId: userId
})v1.1+ Planned: create() will return the model instance and accept data without ID parameter.
save()
async save(
t: string,
id: string,
patch: Record<string, any>,
clientTxId?: string
): Promise<void>Update an existing model.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
t | string | Yes | Model type name |
id | string | Yes | Model ID |
patch | Record<string, any> | Yes | Fields to update |
clientTxId | string | No | Custom transaction ID |
Behavior:
- Merges patch with existing data
- Applies optimistically to pool
- Queues transaction for server
- Returns immediately (optimistic)
Example:
await store.save('task', taskId, {
title: 'Updated Title',
done: true
})remove()
async remove(
t: string,
id: string,
clientTxId?: string
): Promise<void>Delete a model.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
t | string | Yes | Model type name |
id | string | Yes | Model ID |
clientTxId | string | No | Custom transaction ID |
Behavior:
- Removes from pool optimistically
- Queues delete transaction
- Returns immediately (optimistic)
Example:
await store.remove('task', taskId)Hydration Methods
hydrateType()
async hydrateType(t: string): Promise<void>Load all models of a type from storage (no network).
Parameters:
| Parameter | Type | Description |
|---|---|---|
t | string | Model type name |
Behavior:
- Reads all rows of type from storage
- Applies to pool (already in storage, so no persist)
- Skips if type already hydrated
- Prevents duplicate hydration attempts with in-flight guard
- Marks type as hydrated when complete
Example:
// Pre-load all issues from storage
await store.hydrateType('issue')
// Now accessing issue collections is instant
const issues = team.issues.elements // Already loaded âś“
// Check status
if (store.hasHydrated('issue')) {
console.log('Issues ready!')
}ensureRowsByIds()
async ensureRowsByIds(t: string, ids: string[]): Promise<void>Ensure specific models are present locally (from storage or network).
Parameters:
| Parameter | Type | Description |
|---|---|---|
t | string | Model type name |
ids | string[] | Array of model IDs |
Behavior:
- Checks which IDs are missing from pool
- Tries loading from storage first
- Falls back to network batch loader if still missing
- Applies loaded rows to pool and storage
Example:
// Load multiple issues
await store.ensureRowsByIds('issue', ['id-1', 'id-2', 'id-3'])
// Now they're in the pool
const issue1 = useModel<Issue>('issue', 'id-1') // âś“ AvailableMutation Batching
Multiple mutations in the same tick are automatically batched:
// These batch into single server request
await store.save('task', '1', { title: 'A' })
await store.save('task', '2', { title: 'B' })
await store.save('task', '3', { title: 'C' })
// Sent as:
POST /sync/v1/tx {
ops: [
{ t: 'task', id: '1', op: 'u', patch: { title: 'A' } },
{ t: 'task', id: '2', op: 'u', patch: { title: 'B' } },
{ t: 'task', id: '3', op: 'u', patch: { title: 'C' } }
]
}Transaction Queue
Access pending transactions:
// List pending transactions (requires storage adapter method)
const txQueue = await store.storage.listTx()
txQueue.forEach(tx => {
console.log(`${tx.op} ${tx.t}:${tx.mid}`)
})Examples
Complete CRUD
const TaskManager = observer(() => {
const tasks = useCollectionModels<Task>('task')
const store = useStore()
const createTask = async () => {
const id = crypto.randomUUID()
await store.create('task', id, {
title: 'New Task',
done: false
})
}
const updateTask = async (task: Task) => {
await store.save('task', task.id, {
title: task.title,
done: task.done
})
}
const deleteTask = async (taskId: string) => {
await store.remove('task', taskId)
}
return (
<div>
<button onClick={createTask}>Add Task</button>
{tasks.map(task => (
<TaskRow
key={task.id}
task={task}
onUpdate={() => updateTask(task)}
onDelete={() => deleteTask(task.id)}
/>
))}
</div>
)
})With Connection State
const TaskEditor = observer(({ task }) => {
const store = useStore()
const { isOnline, isSyncing } = useConnectionState()
const handleSave = async () => {
await store.save('task', task.id, { title: task.title })
}
return (
<div>
<input value={task.title} onChange={e => task.title = e.target.value} />
<button
onClick={handleSave}
disabled={isSyncing}
>
{isOnline ? 'Save' : 'Save (will sync when online)'}
</button>
</div>
)
})Manual Sync Control
const SyncControls = () => {
const store = useStore()
const handleBootstrap = async () => {
await store.bootstrap()
}
const handleFastForward = async () => {
await store.fastForward()
}
const handleToggleNetwork = () => {
store.networkEnabled = !store.networkEnabled
if (store.networkEnabled) {
// Auto-sync when going back online
store.fastForward()
}
}
return (
<div>
<button onClick={handleBootstrap}>Reload All Data</button>
<button onClick={handleFastForward}>Sync Now</button>
<button onClick={handleToggleNetwork}>
{store.networkEnabled ? 'Go Offline' : 'Go Online'}
</button>
{store.catchingUp && <Spinner>Syncing...</Spinner>}
</div>
)
}See Also
- Model - Model base class with instance methods (v1.1+)
- Hooks - React hooks for accessing models
- SyncProvider - Provider component
- StorageAdapter - Storage adapter interface
- Mutations Guide - Complete mutation guide
- Optimistic Updates - How mutations work