@iron-stack/sync
v1.1.0
Published
Declarative real-time data sync engine -- Socket.IO events automatically update your TanStack Query cache. Define rules once, share them between server and client.
Readme
@iron-stack/sync
Declarative real-time data sync engine -- Socket.IO events automatically update your TanStack Query cache. Define rules once, share them between server and client.
Installation
npm install @iron-stack/syncQuick Start
1. Define sync rules (shared)
import { defineSyncRules } from '@iron-stack/sync';
const syncRules = defineSyncRules<{
'message:new': Message;
'message:updated': Message;
}>({
'message:new': {
room: (msg) => `conversation:${msg.conversationId}`,
actions: [
{
procedure: ['message', 'list'],
action: 'direct',
infinite: true,
match: (msg, input) => input?.conversationId === msg.conversationId,
updater: (old, msg) => prependToFirstPage(old, msg),
},
{
procedure: ['conversation', 'list'],
action: 'invalidate',
},
],
},
'message:updated': {
room: (msg) => `conversation:${msg.conversationId}`,
actions: [
{ procedure: ['message', 'list'], action: 'invalidate' },
],
},
});2. Server -- broadcast events
import { SyncServer } from '@iron-stack/sync/server';
const sync = new SyncServer({ io, rules: syncRules });
// Broadcast to the room defined in rules
sync.broadcast('message:new', newMessage);
// Auto-join users to rooms on connect
io.on('connection', async (socket) => {
await sync.autoJoinRooms(socket, async (userId) => {
const ids = await getUserConversationIds(userId);
return ids.map(id => `conversation:${id}`);
});
});3. Client -- automatic cache updates
import { SyncProvider } from '@iron-stack/sync/client';
function App() {
return (
<SyncProvider rules={syncRules} socket={socket}>
<YourApp />
</SyncProvider>
);
}When message:new arrives via Socket.IO, the message list cache updates instantly -- no HTTP refetch needed.
API Reference
@iron-stack/sync (shared)
| Export | Description |
|---|---|
| defineSyncRules(rules) | Type-safe factory for defining sync rules shared between server and client |
| SyncRules | Type for the complete set of sync rules |
| SyncRule | Type for a single event-to-cache mapping |
| CacheAction | Type for a cache update action (direct, invalidate, or remove) |
@iron-stack/sync/server
| Export | Description |
|---|---|
| SyncServer | Server-side sync engine that manages rooms and broadcasts events |
| SyncServer.broadcast(event, data) | Broadcast an event to the room defined in sync rules |
| SyncServer.joinRoom(room, userIds) | Join all connected sockets for given user IDs to a room |
| SyncServer.autoJoinRooms(socket, resolver) | Auto-join a socket to rooms returned by the resolver function |
| SyncServer.getIO() | Access the underlying Socket.IO server instance |
| SyncServerOptions | Configuration: { io: Server, rules: SyncRules } |
@iron-stack/sync/client
| Export | Description |
|---|---|
| SyncProvider | React provider that connects Socket.IO events to TanStack Query cache |
| useSync() | Hook returning { connected: boolean, socket } |
| createSyncEngine(queryClient, socket, rules) | Low-level engine for non-React usage. Returns a cleanup function |
| SyncProviderProps | Props: { rules, socket, children } |
Cache Action Types
| Action | Behavior |
|---|---|
| "direct" | Updates cache in-place via setQueryData -- fastest, no HTTP request |
| "invalidate" | Marks query as stale, triggers refetch from server -- simplest, always consistent |
| "remove" | Removes the query from cache entirely |
Configuration
CacheAction Options
| Option | Type | Description |
|---|---|---|
| procedure | string[] | tRPC procedure path, e.g. ['message', 'list'] |
| action | "direct" \| "invalidate" \| "remove" | Cache update strategy |
| updater | (old, event) => new | For "direct": function that produces new cache value |
| match | (event, queryInput) => boolean | Only apply when condition matches (e.g., same conversation) |
| infinite | boolean | Whether this targets an infinite query |
License
MIT
