Gluonic
A sync engine that feels synchronous
Build offline-first apps with real-time sync, optimistic updates, and a clean synchronous API - no loading states, no complex async patterns.
What is Gluonic?
Gluonic is an open-source sync engine for React and React Native that handles data synchronization between your app and server. It provides:
- Offline-first - Apps work without network connectivity
- Real-time sync - Changes propagate instantly when online
- Optimistic updates - Instant UI feedback before server confirmation
- Automatic state management - No manual useState/useEffect for synced data
What makes Gluonic unique: A synchronous data access API that makes async data feel instant.
Simple Synchronous API
Gluonic provides two ways to work with async data synchronously:
Pattern 1: Progressive Enhancement
Handle loading states inline - data appears as it loads:
const TeamView = observer(({ team }) => {
const issues = team.issues.elements
const createIssue = async () => {
const issue = new Issue({
title: 'New Issue',
teamId: team.id
})
await issue.save()
}
return (
<div>
<h1>{team.name}</h1>
<button onClick={createIssue}>Create Issue</button>
{issues.map(issue => (
<IssueRow
key={issue.id}
issue={issue}
author={issue.author?.value?.name} // Optional - may be undefined
/>
))}
</div>
)
})Behind the scenes, Gluonic:
- Returns empty array immediately (synchronous)
- Kicks off background loading from storage/network
- MobX re-renders when data arrives
- Progressive appearance - no blocking spinners
Pattern 2: Guaranteed Data with Suspense
Use Suspense boundaries to guarantee data before rendering:
<Suspense fallback={<Spinner />}>
<TeamView team={team} />
</Suspense>
const TeamView = observer(({ team }) => {
const issues = team.issues.suspense // Throws promise until loaded
return issues.map(issue => (
<IssueRow
issue={issue}
author={issue.author.suspense.name} // Guaranteed - no optionals!
/>
))
})Benefits:
- Clean component code (no optional chaining)
- Type-safe (guaranteed non-undefined)
- Declarative loading states
Both patterns avoid async/await in components - that’s what makes them “synchronous”.
Learn more about Synchronous API →
Full Sync Engine Features
Offline-First Architecture
Your app works perfectly without network. Data is stored locally first, then synced to the server in the background.
// Works offline - changes queue for sync
await store.save('task', taskId, { done: true })
// UI updates immediately
// Syncs when connection returnsReal-Time Synchronization
Changes from other users appear instantly via WebSocket + delta sync.
// Another user updates an issue
// Your UI updates automatically
// No polling, no manual refreshOptimistic Updates
UI updates immediately, with automatic rollback on errors.
// Update applies instantly
await store.save('issue', id, { title: 'New Title' })
// If server rejects, automatically rolls back
// No manual error handling neededType-Safe Models
Full TypeScript support with decorators.
@ClientModel('issue')
export class Issue extends Model {
@Property()
id = crypto.randomUUID()
@Property()
title!: string
@Property()
assigneeId?: string
@ManyToOne('assignee', 'assigneeId')
assignee?: User
}Simple Setup
Server
import { SyncServer } from '@gluonic/server'
import { PrismaAdapter } from '@gluonic/server-prisma'
import { JWTAuth } from '@gluonic/auth-jwt'
const database = PrismaAdapter({ prisma })
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET })
})
await server.listen({ port: 3000 })Client
import { SyncClient } from '@gluonic/client'
import { DrizzleAdapter } from '@gluonic/client-drizzle'
import { db } from './db'
import { Issue, User } from './models'
const storage = DrizzleAdapter({ db })
export const client = SyncClient({
server: 'https://api.example.com/sync/v1',
storage,
models: [Issue, User]
})import { SyncProvider } from '@gluonic/client'
import { client } from './sync'
export default function App() {
const { token } = useAuth()
return (
<SyncProvider client={client} token={token}>
<YourApp />
</SyncProvider>
)
}Use in Components
import { observer, useCollectionModels } from '@gluonic/client'
import { Issue } from './models'
const IssueList = observer(() => {
const issues = useCollectionModels<Issue>('issue')
return issues.map(issue => (
<div key={issue.id}>
<h3>{issue.title}</h3>
<span>{issue.assignee.value?.name ?? 'Unassigned'}</span>
</div>
))
})Why Gluonic?
Sync Engine Benefits
- ✅ Offline support out of the box
- ✅ Real-time updates via WebSocket
- ✅ Optimistic UI with automatic rollback
- ✅ Delta synchronization for efficiency
- ✅ Durable transaction queue
- ✅ Conflict resolution
- ✅ Works with any database
Unique Developer Experience
- ✅ Synchronous data access (no async in components)
- ✅ Two patterns: Progressive or Suspense
- ✅ Automatic state management (no useState/useEffect)
- ✅ Lazy loading (data loads on-demand)
- ✅ Object Graph (connected model instances)
- ✅ Identity mapping (same ID = same instance)
Self-Hosted & Open Source
- ✅ Run on your infrastructure
- ✅ Server: Works with Prisma, Drizzle, TypeORM, or any ORM
- ✅ Client: Drizzle adapter for local storage
- ✅ Universal (React Native + Web)
- ✅ No vendor lock-in
Get Started
- New to Gluonic? Quick Start (5 minutes)
- Understand the concepts? Core Concepts
- Want the details? Architecture
Status
Gluonic is in active development. The core sync engine is functional and we’re working toward a stable v1.0 release.