board.storage - Shared State
Synchronize component state between all participants in real-time.
Overview
board.storage is the primary API for data synchronization. All operations automatically sync across participants via WebSocket.
Basic Operations
get() - Read Value
typescript
// Get value (returns undefined if not set)
const count = board.storage.get<number>('count');
// Get with default value
const count = board.storage.get<number>('count', 0);set() - Write Value
typescript
// Set value (automatically syncs to all participants)
await board.storage.set('count', 5);
// Set multiple values
await board.storage.set('user', { name: 'Alice', score: 100 });update() - Atomic Update
typescript
// Atomic increment (prevents race conditions)
await board.storage.update<number>('count', (prev) => (prev || 0) + 1);
// Update object
await board.storage.update('user', (prev) => ({
...prev,
score: (prev?.score || 0) + 10
}));delete() - Remove Value
typescript
await board.storage.delete('count');Subscriptions
subscribe() - Watch for Changes
typescript
// Subscribe to specific key
const unsubscribe = board.storage.subscribe<number>('count', (newValue, oldValue) => {
console.log(`Count changed: ${oldValue} → ${newValue}`);
updateUI(newValue);
});
// Unsubscribe when done
unsubscribe();subscribeAll() - Watch All Changes
typescript
board.storage.subscribeAll((changes) => {
// changes: Array<{ key: string, newValue: any, oldValue: any }>
changes.forEach(change => {
console.log(`${change.key} changed to ${change.newValue}`);
});
});Get All State
typescript
const fullState = board.storage.getAll();
// { count: 5, user: { name: 'Alice', score: 100 } }Examples
Counter Component
typescript
import { BoardAPI } from '@boardapi/sdk';
const board = new BoardAPI();
// Initialize counter
board.on('ready', async () => {
const count = board.storage.get<number>('count', 0);
updateDisplay(count);
});
// Increment button
document.querySelector('#increment').addEventListener('click', async () => {
await board.storage.update<number>('count', (prev) => (prev || 0) + 1);
});
// Listen to changes from other users
board.storage.subscribe<number>('count', (newValue) => {
updateDisplay(newValue);
});
function updateDisplay(value: number) {
document.querySelector('#count').textContent = value.toString();
}Voting Component
typescript
interface Vote {
userId: string;
value: number;
timestamp: string;
}
// Cast vote
async function castVote(value: number) {
const votes = board.storage.get<Vote[]>('votes', []);
await board.storage.set('votes', [
...votes,
{
userId: board.participants.me.id,
value,
timestamp: new Date().toISOString()
}
]);
}
// Watch for new votes
board.storage.subscribe<Vote[]>('votes', (votes) => {
renderVotes(votes);
updateStats(votes);
});Form Component
typescript
interface FormData {
name: string;
email: string;
message: string;
}
// Auto-save form
const form = document.querySelector<HTMLFormElement>('#myForm');
form.addEventListener('input', async (e) => {
const target = e.target as HTMLInputElement;
await board.storage.update<FormData>('formData', (prev) => ({
...prev,
[target.name]: target.value
}));
});
// Restore form on load
board.on('ready', () => {
const formData = board.storage.get<FormData>('formData');
if (formData) {
Object.entries(formData).forEach(([key, value]) => {
const input = form.querySelector<HTMLInputElement>(`[name="${key}"]`);
if (input) input.value = value;
});
}
});Private Storage (Advanced)
Store data that's not visible to other participants:
typescript
// Private value (only visible to current user)
await board.storage.setPrivate('myVote', 8);
// Private with limited visibility
await board.storage.setPrivate('myAnswer', 'B', {
visibleTo: ['teacher', 'admin']
});
// Anonymous value (no author tracking)
await board.storage.setAnonymous('feedback', 'Great session!');
// Host reveals all private values
await board.server.reveal('myVote');
// Listen for reveal
board.on('storage:revealed', (key, values) => {
// values: [{ value: 8, participantId: 'user-1' }, ...]
showResults(values);
});Use cases:
- Planning Poker - Hide votes until reveal
- Anonymous Surveys - Collect feedback anonymously
- Exams - Prevent cheating
Transient State (High-Frequency Updates)
For smooth animations and drag-and-drop:
typescript
// Slider - smooth real-time updates
slider.addEventListener('input', (e) => {
// High-frequency updates (60+ FPS)
board.storage.setTransient('sliderValue', e.target.value);
});
// Commit to database when done
slider.addEventListener('change', (e) => {
board.storage.set('sliderValue', e.target.value);
});
// Listen to transient updates
board.storage.onTransient('sliderValue', (value) => {
animateSlider(value);
});Transient vs Regular:
- Transient: RAM only, no DB, high frequency (60/sec)
- Regular: Persisted to DB, undo/redo, throttled (10/sec)
Rate Limits
- Regular storage: 30 updates/sec per component
- Transient storage: 60 updates/sec per key
- Max payload: 100KB per operation
Exceeding limits returns error:
typescript
{
success: false,
error: 'Rate limit exceeded (max 30 updates/sec)'
}TypeScript Types
typescript
interface MyStorage {
count: number;
votes: Array<{ userId: string; value: number }>;
formData: { name: string; email: string };
}
const board = new BoardAPI<MyStorage>();
// Fully typed!
const count = board.storage.get('count'); // number | undefined
board.storage.set('unknownKey', 123); // TypeScript Error!Best Practices
- Use
update()for counters - Prevents race conditions - Subscribe early - Subscribe before setting initial values
- Unsubscribe on cleanup - Prevent memory leaks
- Use transient for animations - Keep DB clean
- Validate data - Always validate user input
What's Next?
- Props API - Read component configuration
- Participants API - Get user information
- Offline Support - Handle disconnections