High-performance state synchronization library with TypeScript 5.0+ decorators
npm install @roomkit/stateHigh-performance state synchronization library with TypeScript 5.0+ decorators.
- 🎯 TypeScript 5.0+ Decorators: Now using standard decorators (no more experimentalDecorators!)
- ⚡ Better Type Safety: Improved type inference and IDE support
- 📦 Smaller Bundle: Removed reflect-metadata dependency
- 🎮 Zero Breaking Changes: Same API, better internals
- 🎯 Type-Safe: Decorator-based schema definition with TypeScript 5.0+ support
- ⚡ High Performance: Binary encoding with MessagePack, <1ms for 1000 entities
- 📦 Small Payloads: 75-85% bandwidth reduction with compression
- 🔄 Automatic Tracking: Automatic change detection for all data types
- 🎮 Game-Ready: Tracked collections (Map, Array, Set) with automatic sync
- 📊 Delta Updates: JSON Patch format for incremental state changes
- 🧩 Deep Nesting: Automatic tracking of nested objects and collections
- 🗜️ Smart Compression: Adaptive compression with entropy detection
- 📦 Batch Updates: Queue system to reduce message frequency by 20x
- 📈 Performance Monitoring: Built-in statistics and benchmarking
``bash`
pnpm add @roomkit/state
Requirements: TypeScript 5.0+ (for decorator support)
| Scale | Players | Encoding Time | Data Size | Compression | Throughput |
|-------|---------|---------------|-----------|-------------|------------|
| Small | 10 | 0.087ms | 474 bytes | 58.1% saved | 11,504/sec |
| Medium| 100 | 0.210ms | 3.3 KB | 70.6% saved | 4,768/sec |
| Large | 1000 | 0.996ms | 32 KB | 72.5% saved | 1,004/sec |
Delta Updates: 0.006ms for 3 patches, 158,430 patches/sec
Batch Queue: 20x message reduction, 4.25ms for 5000 patches
Change Tracking: 0.010ms per cycle, 104,261 cycles/sec
`typescript
import { State, Field, MapField, TrackedMap } from '@roomkit/state';
class Player {
@Field('string') name: string = '';
@Field('number') score: number = 0;
}
class GameState extends State {
@MapField(Player) players = new Map
@Field('string') status: string = 'waiting';
}
// Usage
const state = new GameState();
state.startTracking(); // Automatically converts to TrackedMap
// All modifications are automatically tracked
state.players.set('p1', newPlayer);
state.status = 'playing';
// Get changes
const patches = state.getPatches(); // JSON Patch format
const encoded = state.encode(); // Binary MessagePack
// Clear changes after sync
state.clearChanges();
`
`typescript
import { TrackedMap, TrackedArray, TrackedSet } from '@roomkit/state';
// TrackedMap
const players = new TrackedMap
players.startTracking();
players.onChange((patches) => {
console.log('Players changed:', patches);
});
players.set('p1', new Player());
// Triggers onChange with patches
// TrackedArray
const items = new TrackedArray
items.startTracking();
items.push('item1', 'item2');
// TrackedSet
const tags = new TrackedSet
tags.startTracking();
tags.add('tag1');
`
`typescript
import { StateDecoder } from '@roomkit/state/encoding';
class ClientGameState {
players = new Map
scores: number[] = [];
status: string = 'waiting';
round: number = 0;
}
const decoder = new StateDecoder();
const state = new ClientGameState();
// Full state sync
room.onMessage(MessageId.STATE_FULL, (message) => {
const decoded = decoder.decode(message.data);
Object.assign(state, decoded);
});
// Delta updates
room.onMessage(MessageId.STATE_DELTA, (message) => {
decoder.applyPatches(state, message.patches);
});
`
#### @Field(type: FieldType)
Define a primitive field.
`typescript`
@Field('string') name: string = '';
@Field('number') health: number = 100;
@Field('boolean') isAlive: boolean = true;
`typescript`
@MapField(Player) players = new Map
When startTracking() is called, automatically converts to TrackedMap which:set()
- Tracks , delete(), clear() operationsonChange(callback)
- Emits change events via batch(fn)
- Supports batch operations with
`typescript`
@ArrayField('number') scores: number[] = [];
Automatically converts to TrackedArray which tracks:push()
- , pop(), shift(), unshift()splice()
- , sort(), reverse()
- Array element assignments
`typescript`
@SetField('string') tags = new Set
Automatically converts to TrackedSet which tracks:add()
- , delete(), clear()
- All modifications to the set
#### startTracking()
Start tracking changes to the state.
#### stopTracking()
Stop tracking changes.
#### getPatches(): Patch[]
Get accumulated changes as JSON Patch operations.
#### clearChanges()
Clear accumulated changes.
#### encode(full?: boolean, options?: EncodeOptions): EncodedStatefull=true
Encode state to binary format.
- : Encode entire statefull=false
- : Encode only changes (delta){ data, compressed, originalSize, compressedSize, compressionRatio }
- Returns:
#### clone(): this
Create a deep copy of the state.
`typescript
import { CompressionStrategy, StateEncoder } from '@roomkit/state';
// Create custom compression strategy
const compressionStrategy = new CompressionStrategy({
threshold: 2048, // Only compress data > 2KB
minCompressionRatio: 0.7, // Skip if compression ratio > 70%
level: 9, // Compression level (1-9)
adaptive: true // Learn from compression history
});
// Use with encoder
const encoder = new StateEncoder(compressionStrategy);
const encoded = encoder.encodeFull(state);
// Get compression statistics
const stats = encoder.getCompressionStats();
console.log(Compression rate: ${stats.compressionRate * 100}%);Average saving: ${stats.averageSaving} bytes
console.log();`
`typescript
import { BatchQueue } from '@roomkit/state';
const queue = new BatchQueue({
maxWaitTime: 100, // Flush every 100ms
maxPatchCount: 50, // Or when 50 patches accumulated
maxBatchSize: 10240, // Or when 10KB reached
enablePriority: true // Enable priority queue
});
// Register flush callback
queue.onFlush((updates) => {
const patches = updates.flatMap(u => u.patches);
const encoded = encoder.encodeDelta(patches);
sendToClients(encoded.data);
});
// Add updates to queue
setInterval(() => {
const patches = state.getPatches();
if (patches.length > 0) {
queue.enqueue(patches, priority);
state.clearChanges();
}
}, 16); // 60 FPS
// Force flush on important events
queue.forceFlush();
// Get statistics
const stats = queue.getStats();
console.log(Batching factor: ${stats.totalEnqueued / stats.totalBatches}x);`
`typescript
import {
State, Field, MapField,
StateEncoder, StateDecoder,
CompressionStrategy, BatchQueue
} from '@roomkit/state';
class GameState extends State {
@MapField(Player) players = new Map();
@Field('number') tick = 0;
}
// Server setup
const state = new GameState();
state.startTracking();
const compressionStrategy = new CompressionStrategy({ adaptive: true });
const encoder = new StateEncoder(compressionStrategy);
const queue = new BatchQueue({ maxWaitTime: 50 });
queue.onFlush((updates) => {
const patches = updates.flatMap(u => u.patches);
// Optimize patches (merge, deduplicate)
const optimized = BatchQueue.optimizePatches(patches);
// Encode with compression
const encoded = encoder.encodeDelta(optimized);
// Send to clients
broadcast({
type: 'state_delta',
data: encoded.data,
compressed: encoded.compressed
});
// Log stats
console.log(Sent ${encoded.compressedSize} bytes (${optimized.length} patches));
});
// Game loop
setInterval(() => {
// Update game logic
updateGame(state);
// Queue changes
const patches = state.getPatches();
if (patches.length > 0) {
queue.enqueue(patches);
state.clearChanges();
}
}, 16); // 60 FPS
`
periodically to avoid stat accumulation
- Use forceFlush() at critical moments
- Consider state sharding for large-scale applicationsBenchmarking
Run the built-in performance tests:
`bash
cd packages/state
pnpm benchmark
``This will test:
- Full state encoding (with/without compression)
- Delta encoding performance
- Decoding performance
- Change tracking overhead
- Batch queue efficiency
- Compression strategy effectiveness
- Full state encoding: 0.087ms (10 players) → 0.996ms (1000 players)
- Delta encoding: 0.006ms for 3 patches, 158,430 patches/sec
- Compression: 58-73% size reduction for game states
- Batch queue: 20x message frequency reduction
- Bandwidth reduction: 75-85% vs JSON
- Phase 1 Summary - Core state system
- Phase 2 Summary - Tracked collections
- Phase 3 Summary - Compression & optimization
MIT