Installation
npm install @insforge/sdk@latest
import { createClient } from '@insforge/sdk';
const insforge = createClient({
baseUrl: 'https://your-app.insforge.app',
anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests
});
Mental Model
The TypeScript SDK opens one Socket.IO connection to your InsForge backend. You subscribe to named channels, listen for event names, and optionally publish events back to channels you have joined.
Events can come from two places:
- Database triggers that call
realtime.publish(channel, event, payload).
- Clients that call
insforge.realtime.publish(channel, event, payload).
For the backend channel and RLS model, see Realtime overview.
Quick Start
import { createClient } from '@insforge/sdk';
const insforge = createClient({
baseUrl: 'https://your-app.insforge.app',
anonKey: 'your-anon-key'
});
insforge.realtime.on('connect', () => {
console.log('Connected:', insforge.realtime.socketId);
});
insforge.realtime.on('error', (error) => {
console.error(error.code, error.message);
});
await insforge.realtime.connect();
const subscription = await insforge.realtime.subscribe('order:123');
if (!subscription.ok) {
throw new Error(subscription.error.message);
}
insforge.realtime.on('status_changed', (message) => {
console.log(message.status);
console.log(message.meta.messageId);
});
Register connect, disconnect, connect_error, and error handlers before calling connect() so early connection failures are visible.
connect()
Establish a WebSocket connection.
await insforge.realtime.connect();
Returns:
Notes:
- The SDK includes the current auth token when one exists. If there is no signed-in user, it can use the configured anon key.
- Multiple
connect() calls while a connection is already in progress reuse the same connection promise.
- The connection attempt times out after 10 seconds.
subscribe()
Subscribe to a channel and receive the current presence snapshot.
const response = await insforge.realtime.subscribe('chat:room-1');
if (response.ok) {
console.log(response.channel);
console.log(response.presence.members);
} else {
console.error(response.error.code, response.error.message);
}
Parameters:
| Parameter | Type | Description |
|---|
channel | string | Resolved channel name, such as orders, order:123, or chat:room-1. |
Returns:
type SubscribeResponse =
| {
ok: true;
channel: string;
presence: {
members: PresenceMember[];
};
}
| {
ok: false;
channel: string;
error: {
code: string;
message: string;
};
};
subscribe() auto-connects if needed. Calling connect() explicitly is still recommended so connection event handlers are already attached.
publish()
Publish an event to a channel.
await insforge.realtime.publish('chat:room-1', 'new_message', {
text: 'Hello',
sentAt: new Date().toISOString()
});
Parameters:
| Parameter | Type | Description |
|---|
channel | string | Channel to publish to. The client must already be subscribed. |
event | string | Event name that subscribers listen for. |
payload | Record<string, unknown> | JSON-serializable message payload. |
Publishing requires a prior successful subscription to the same channel. If RLS is enabled on realtime.messages, publish is also checked against INSERT policies.
Publish failures are emitted through the error event.
on()
Listen for custom events, connection events, presence events, and realtime errors.
insforge.realtime.on('new_message', (message) => {
console.log(message.text);
console.log(message.meta.senderType);
});
Reserved events:
| Event | Payload | Description |
|---|
connect | None | The WebSocket connected. |
connect_error | Error | Initial connection or reconnect failed. |
disconnect | string | The WebSocket disconnected. |
error | RealtimeErrorPayload | Subscribe or publish failed. |
presence:join | PresenceJoinMessage | A logical member became present in a channel. |
presence:leave | PresenceLeaveMessage | A logical member is no longer present in a channel. |
once()
Listen for an event once, then remove the listener automatically.
insforge.realtime.once('checkout_completed', (message) => {
console.log('Completed:', message.orderId);
});
off()
Remove an event listener.
function handleStatus(message: OrderStatusMessage) {
console.log(message.status);
}
insforge.realtime.on('status_changed', handleStatus);
insforge.realtime.off('status_changed', handleStatus);
unsubscribe()
Leave a channel.
insforge.realtime.unsubscribe('chat:room-1');
unsubscribe() is fire-and-forget. If this was the final socket for a logical member, other subscribers receive presence:leave.
disconnect()
Close the WebSocket and clear local subscriptions.
insforge.realtime.disconnect();
Message Shape
Delivered messages include your payload fields plus server metadata.
import type { SocketMessage } from '@insforge/sdk';
interface OrderStatusMessage extends SocketMessage {
id: string;
status: string;
}
insforge.realtime.on<OrderStatusMessage>('status_changed', (message) => {
console.log(message.id);
console.log(message.status);
console.log(message.meta.messageId);
console.log(message.meta.senderType);
console.log(message.meta.senderId);
console.log(message.meta.timestamp);
});
Metadata:
interface SocketMessageMeta {
channel?: string;
messageId: string;
senderType: 'system' | 'user';
senderId?: string;
timestamp: string;
}
senderType is system for database-triggered messages and user for client-published messages.
Presence
A successful subscription returns the current presence snapshot.
const response = await insforge.realtime.subscribe('chat:room-1');
if (response.ok) {
for (const member of response.presence.members) {
console.log(member.type, member.presenceId, member.joinedAt);
}
}
Presence member:
type PresenceMember =
| {
type: 'user';
presenceId: string;
joinedAt: string;
}
| {
type: 'anonymous';
presenceId: string;
joinedAt: string;
};
Listen for changes:
insforge.realtime.on('presence:join', (message) => {
console.log('Joined:', message.member.presenceId);
});
insforge.realtime.on('presence:leave', (message) => {
console.log('Left:', message.member.presenceId);
});
Properties
| Property | Type | Description |
|---|
isConnected | boolean | Whether the socket is currently connected. |
connectionState | 'disconnected' | 'connecting' | 'connected' | Current SDK connection state. |
socketId | string | undefined | Socket.IO ID when connected. |
getSubscribedChannels() | () => string[] | Local list of subscribed channels. |
Error Handling
insforge.realtime.on('connect_error', (error) => {
console.error('Connection failed:', error.message);
});
insforge.realtime.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
});
insforge.realtime.on('error', (error) => {
console.error(error.channel, error.code, error.message);
});
Common realtime error codes:
| Code | Meaning |
|---|
REALTIME_UNAUTHORIZED | RLS or channel matching denied subscribe or publish. |
REALTIME_NOT_SUBSCRIBED | The client tried to publish before subscribing to the channel. |
INTERNAL_ERROR | The backend could not complete the realtime operation. |
Complete Example
const channel = `order:${orderId}`;
insforge.realtime.on('error', (error) => {
console.error(error.code, error.message);
});
await insforge.realtime.connect();
const response = await insforge.realtime.subscribe(channel);
if (!response.ok) {
throw new Error(response.error.message);
}
insforge.realtime.on('status_changed', (message) => {
renderOrderStatus(message.status);
});
await insforge.realtime.publish(channel, 'customer_viewed', {
orderId,
viewedAt: new Date().toISOString()
});
function cleanup() {
insforge.realtime.unsubscribe(channel);
insforge.realtime.disconnect();
}