Skip to Content

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: ObjectPool

Access 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: number

Latest sync ID received from server. Used for delta sync.

networkEnabled

networkEnabled: boolean

Control network connectivity. Set to false to pause sync.

// Go offline store.networkEnabled = false // Go online store.networkEnabled = true await store.fastForward() // Catch up

Observable State

bootstrapping

bootstrapping: boolean

true when running initial bootstrap from server.

fastForwarding

fastForwarding: boolean

true when running delta sync (catching up with server).

replayingQueue

replayingQueue: boolean

true when replaying pending transaction queue.

initializingSession

initializingSession: boolean

true when initializing a new session (covers entire bootstrap lifecycle).

catchingUp

get catchingUp(): boolean

Computed 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(): boolean

Alias for replayingQueue (getter for consistency).

isInitializingSession

get isInitializingSession(): boolean

Alias for initializingSession (getter for consistency).


Type Hydration Status

isHydrating()

isHydrating(type: string): boolean

Check if a specific type is currently hydrating from storage.

Parameters:

ParameterTypeDescription
typestringModel type name

Returns: boolean - true if currently hydrating

Example:

if (store.isHydrating('task')) { return <Spinner>Loading tasks...</Spinner> }

hasHydrated()

hasHydrated(type: string): boolean

Check if a specific type has completed hydration from storage.

Parameters:

ParameterTypeDescription
typestringModel 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 lastSyncId from 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 bootstrapping observable 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 fastForwarding observable 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:

ParameterTypeRequiredDescription
tstringYesModel type name
idstringYesModel ID (usually UUID)
patchRecord<string, any>YesInitial data
clientTxIdstringNoCustom 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:

ParameterTypeRequiredDescription
tstringYesModel type name
idstringYesModel ID
patchRecord<string, any>YesFields to update
clientTxIdstringNoCustom 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:

ParameterTypeRequiredDescription
tstringYesModel type name
idstringYesModel ID
clientTxIdstringNoCustom 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:

ParameterTypeDescription
tstringModel 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:

ParameterTypeDescription
tstringModel type name
idsstring[]Array of model IDs

Behavior:

  1. Checks which IDs are missing from pool
  2. Tries loading from storage first
  3. Falls back to network batch loader if still missing
  4. 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') // âś“ Available

Mutation 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

Last updated on