You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Journal mode: WAL (Write-Ahead Logging) for concurrent reads/writes
Database file: objectstack_offline.db
Storage pattern: Document store (JSON blobs in TEXT columns)
Sync Queue
Queue Table Schema
CREATETABLEsync_queue (
id INTEGERPRIMARY KEY AUTOINCREMENT,
object_name TEXTNOT NULL,
record_id TEXTNOT NULL,
operation TEXTNOT NULLCHECK (operation IN ('create','update','delete')),
payload TEXTNOT NULL DEFAULT '{}', -- JSON mutation data
status TEXTNOT NULL DEFAULT 'pending'CHECK (status IN ('pending','in_progress','failed','conflict')),
retries INTEGERNOT NULL DEFAULT 0,
error_message TEXT,
created_at INTEGERNOT NULL,
updated_at INTEGERNOT NULL
);
Queue Entry Lifecycle
enqueueMutation() → status: 'pending'
↓
markInProgress(id) → status: 'in_progress'
↓
┌─── success ───┐ ┌─── error ────┐ ┌─── conflict ──┐
↓ ↓ ↓
markCompleted(id) markFailed(id) markConflict(id)
(DELETE row) ↓ ↓
resetEntry(id) User resolves →
→ back to 'pending' resetEntry(id)
or discardEntry(id)
Queue API
Function
Description
bootstrapSyncQueue()
Create queue table (call at startup)
enqueueMutation(object, id, op, payload)
Add mutation to queue
getPendingEntries()
Get pending entries (FIFO order)
getAllQueueEntries()
Get all entries (for UI display)
getConflictEntries()
Get entries needing conflict resolution
getPendingCount()
Count pending + failed entries
markInProgress(id)
Mark entry as being processed
markCompleted(id)
Delete entry on success
markFailed(id, error)
Mark as failed, increment retry count
markConflict(id, error)
Mark as conflict, need user input
resetEntry(id)
Reset to pending for retry
discardEntry(id)
Drop entry (user chose to abandon)
clearSyncQueue()
Clear entire queue
Sync State Store
// stores/sync-store.tsinterfaceSyncState{isSyncing: boolean;// Is a sync cycle running?pendingCount: number;// Badge count for UIlastSyncedAt: number|null;// Last successful sync timestampconflicts: SyncQueueEntry[];// Entries needing user resolution}
Background Sync
Task Registration
// lib/background-sync.tsconstBACKGROUND_SYNC_TASK="objectstack-background-sync";// Registered via expo-task-managerTaskManager.defineTask(BACKGROUND_SYNC_TASK,async()=>{// 1. Check connectivity// 2. Get pending entries// 3. Signal OS there is work to do// 4. Foreground sync hook drains on next launch});// Register at app startupawaitBackgroundFetch.registerTaskAsync(BACKGROUND_SYNC_TASK,{minimumInterval: 15*60,// 15 minutes (OS minimum)stopOnTerminate: false,startOnBoot: true,});
Default TTL: 5 minutes. After expiration, the next access triggers a conditional request with the stored ETag.
Cache API
Function
Description
getCachedMetadata(key)
Get cached entry
setCachedMetadata(key, data, etag?)
Store entry with optional ETag
isCacheFresh(key, ttl?)
Check if entry is within TTL
getCachedETag(key)
Get stored ETag for conditional request
clearMetadataCache()
Clear all cached metadata
removeCachedMetadata(key)
Remove specific entry
ETag-Based Invalidation Flow
1. Client requests metadata
2. Cache hit + fresh → return cached
3. Cache hit + stale → send request with If-None-Match: <etag>
a. 304 Not Modified → refresh TTL, return cached
b. 200 OK → update cache with new data + etag
4. Cache miss → send request without etag
→ store response in cache
Data Hook Reference
SDK Hooks (from @objectstack/client-react)
Hook
Purpose
Returns
useClient()
Access raw ObjectStack client
ObjectStackClient
useQuery(object, options?)
Fetch records with filters
{ data, isLoading, error, refetch }
useMutation(object)
Create/update/delete mutations
{ create, update, remove }
usePagination(object, options?)
Paginated record fetching
{ data, page, nextPage, prevPage }
useInfiniteQuery(object, options?)
Infinite scroll loading
{ data, fetchNextPage, hasNextPage }
useObject(name)
Fetch object schema
{ data: ObjectDefinition }
useView(object, type?)
Fetch view metadata
{ data: ViewMeta }
useFields(object)
Fetch field definitions
{ data: FieldDefinition[] }
useMetadata(type, name)
Generic metadata fetch
{ data }
Custom Hooks
Hook
File
Purpose
useAppDiscovery()
hooks/useAppDiscovery.ts
Discover installed apps via client.packages.list()
useViewStorage()
hooks/useViewStorage.ts
CRUD for saved views via client.views.*
useBatchOperations()
hooks/useBatchOperations.ts
Multi-record batch operations
useFileUpload()
hooks/useFileUpload.ts
File upload with progress tracking
useAnalyticsQuery()
hooks/useAnalyticsQuery.ts
Analytics data via client.analytics.query()
useAnalyticsMeta()
hooks/useAnalyticsMeta.ts
Analytics metadata via client.analytics.meta()
useOfflineSync()
hooks/useOfflineSync.ts
Offline sync management
useNetworkStatus()
hooks/useNetworkStatus.ts
Real-time network connectivity
useQueryBuilder()
hooks/useQueryBuilder.ts
Filter construction state management
useDashboardData()
hooks/useDashboardData.ts
Dashboard widget data loading
Performance Considerations
Query Optimization
Strategy
Implementation
Pagination
Default page size from view metadata; infinite scroll
This document details the data layer as implemented through Phase 3. See ARCHITECTURE.md for the overall system architecture and ROADMAP.md for upstream SDK dependencies.