A powerful synchronization library for real-time state management and persistence in TypeScript applications. This package is part of the Signe framework and provides decorators and utilities for seamless state synchronization between client and server.
- 🔄 Real-time state synchronization
- 💾 State persistence
- 🎯 Selective synchronization with fine-grained control
- 🔌 WebSocket integration with PartySocket
- 🎨 Decorator-based API for easy implementation
- 🔍 Path-based value loading and retrieval
- 📦 TypeScript support out of the box
npm install @signe/sync
import { signal } from '@signe/reactive'
import { sync, syncClass } from '@signe/sync'
class MyClass {
@sync()
count = signal(0)
@sync()
text = signal('hello')
}
const instance = new MyClass()
syncClass(instance, {
onSync: (cache) => console.log('Sync cache:', cache),
onPersist: (cache) => console.log('Persist cache:', cache)
})
Synchronizes a property with optional settings:
class MyClass {
// Basic sync with default options
@sync()
basicProp = signal(0)
}
You can synchronize collections of objects by specifying the class type:
class Player {
@id() id = signal('player-1')
@sync() name = signal('Player Name')
}
class MyClass {
// Synchronize a collection of Player objects
@sync(Player) players = signal<Record<string, Player>>({})
addPlayer(playerId: string) {
// Dynamic key synchronization
// The Player instance automatically gets the id from the key
this.players()[playerId] = new Player()
}
}
In the example above, when you add a player with players.value['player-123'] = new Player()
, the @id()
decorator ensures the Player instance automatically takes 'player-123' as its ID.
There are two ways to synchronize objects:
- Entire object synchronization:
class MyClass {
// The entire object is synchronized as one unit
@sync() myObj = signal({ val: 1, count: 2 })
}
- Granular property synchronization:
class MyClass {
// Individual properties with signals are synchronized separately
@sync() myObject = {
val: signal(1),
count: signal(2)
}
}
The key difference:
- In the first approach, changing any property triggers synchronization of the entire object
- In the second approach, only the changed property is synchronized, providing finer-grained control
Marks a property as the unique identifier for an instance:
class Player {
// Will automatically receive the key value when added to a collection
@id() id = signal('')
@sync() name = signal('Player Name')
}
The @id()
decorator is especially useful for dynamic collections where the key in the collection should be reflected in the object's ID property.
Marks a property for special user collection synchronization:
class User {
@id() id = signal('')
@sync() name = signal('')
@connected() isConnected = signal(false)
}
class Room {
// Special collection that automatically populates based on user connections
@users(User) connectedUsers = signal<Record<string, User>>({})
}
The @users()
decorator creates a special collection that:
- Automatically populates with user instances when they connect to the room
- Automatically removes users when they disconnect
- Links to the user's session information
- Updates all clients in real-time with connection status
This is ideal for building features like user presence indicators, online user lists, or real-time collaboration tools.
Marks a property for persistence only (no client sync):
class MyClass {
@persist() myPersistentProp = signal(0)
}
Marks a property for tracking user connection status:
class User {
@id() id = signal('user-1')
@connected() isConnected = signal(false)
name = signal('User Name')
}
This decorator automatically tracks and synchronizes a user's connection state. When a user connects to a room, the property is automatically set to true
. When they disconnect, it's set to false
. This state is synchronized with all clients, allowing real-time connection status updates without manual management.
Benefits:
- Automatically updated when users connect/disconnect
- Synchronized to all clients in real-time
- Can be used in UI to show online/offline indicators
- No need to manually track connection status with custom events
Set up a WebSocket connection for real-time synchronization:
import { connectionRoom } from '@signe/sync/client'
const room = new Room()
const conn = connectionRoom({
host: 'your-server-url',
room: 'room-id'
}, room)
// Emit events
conn.emit('event-name', { data: 'value' })
// Listen for events
conn.on('event-name', (data) => {
console.log('Received:', data)
})
Load state from paths or objects:
import { load } from '@signe/sync'
// Load using paths
load(instance, {
'position.x': 10,
'position.y': 20
})
// Load using object
load(instance, {
position: { x: 10, y: 20 }
}, true)
Synchronizes an instance by adding state management methods.
Options:
-
onSync?: (value: Map<string, any>) => void
- Callback for sync events -
onPersist?: (value: Set<string>) => void
- Callback for persistence events
Common options for decorators:
-
classType?: Function
- Specify a class type for complex objects -
persist?: boolean
- Enable/disable persistence (default: true) -
syncToClient?: boolean
- Enable/disable client synchronization (default: true)
MIT