Client API
The TopGunClient is the main entry point for interacting with the TopGun cluster.
It handles connection management, offline synchronization, and provides access to distributed data structures.
Constructor
new TopGunClient(config)Parameters
serverUrlstring. WebSocket URL of the TopGun server (e.g., ws://localhost:8765). Required for single-server mode.cluster?ClusterConfig. Cluster configuration for multi-node deployments. When provided, serverUrl is ignored.storageIStorageAdapter. Persistence adapter (e.g., IDBAdapter).nodeId?string. Optional unique ID for this client. Auto-generated if omitted.
Single-Server Mode
For simple deployments with a single TopGun server:
import { TopGunClient, IDBAdapter } from '@topgunbuild/client';
const client = new TopGunClient({
serverUrl: 'ws://localhost:8765',
storage: new IDBAdapter(),
}); Cluster Mode
For production deployments with multiple nodes, use the cluster configuration:
import { TopGunClient, IDBAdapter } from '@topgunbuild/client';
const client = new TopGunClient({
cluster: {
seeds: [
'ws://node1.example.com:8765',
'ws://node2.example.com:8765',
'ws://node3.example.com:8765',
],
smartRouting: true, // Enable partition-aware routing
connectionsPerNode: 2, // Connection pool size per node
connectionTimeoutMs: 5000, // Connection timeout
},
storage: new IDBAdapter(),
}); Smart Routing
When smartRouting is enabled,
the client automatically routes operations to the correct partition owner node. This reduces network hops and improves latency.
The client maintains a partition map and updates it automatically when the cluster topology changes.
ClusterConfig Options
interface ClusterConfigParameters
seedsstring[]. List of seed node WebSocket URLs. The client connects to these nodes to discover the cluster.smartRouting?boolean. Enable partition-aware routing. Operations are sent directly to partition owners. Default: true.connectionsPerNode?number. Number of connections to maintain per node. Default: 1.partitionMapRefreshMs?number. Interval for refreshing the partition map. Default: 30000 (30 seconds).connectionTimeoutMs?number. Connection timeout in milliseconds. Default: 10000.retryAttempts?number. Number of retry attempts on connection failure. Default: 3.
HTTP Sync Mode
For serverless environments where WebSocket connections are unavailable, use HttpSyncProvider to sync via HTTP polling. HttpSyncProvider implements the IConnectionProvider interface and is used with SyncEngine directly — it is not compatible with the TopGunClient constructor, which only accepts serverUrl and cluster options.
import { HttpSyncProvider, SyncEngine } from '@topgunbuild/client';
import { HLC } from '@topgunbuild/core';
import { IDBAdapter } from '@topgunbuild/adapters';
const hlc = new HLC('client-1');
const provider = new HttpSyncProvider({
url: 'https://your-api.example.com',
clientId: 'client-1',
hlc,
authToken: 'your-jwt-token',
syncMaps: ['todos'],
pollIntervalMs: 5000,
});
const engine = new SyncEngine({
nodeId: 'client-1',
connectionProvider: provider,
storageAdapter: new IDBAdapter(),
}); HttpSyncProviderConfig
interface HttpSyncProviderConfigParameters
urlstring. HTTP URL of the TopGun server (e.g., 'https://api.example.com').clientIdstring. Client identifier for request construction.hlcHLC. Hybrid Logical Clock instance for causality tracking.authToken?string. JWT auth token for the Authorization header.pollIntervalMs?number. Polling interval in milliseconds. Default: 5000requestTimeoutMs?number. HTTP request timeout in milliseconds. Default: 30000syncMaps?string[]. Map names to sync deltas for on each poll.fetchImpl?typeof fetch. Custom fetch implementation for testing or platform compatibility.
Auto Connection Mode
AutoConnectionProvider automatically tries WebSocket first and falls back to HTTP sync when WebSocket connections fail. Like HttpSyncProvider, it implements the IConnectionProvider interface and is used with SyncEngine directly — not with TopGunClient.
import { AutoConnectionProvider, SyncEngine } from '@topgunbuild/client';
import { HLC } from '@topgunbuild/core';
import { IDBAdapter } from '@topgunbuild/adapters';
const hlc = new HLC('client-1');
const provider = new AutoConnectionProvider({
url: 'wss://your-api.example.com',
clientId: 'client-1',
hlc,
authToken: 'your-jwt-token',
maxWsAttempts: 3,
syncMaps: ['todos'],
});
const engine = new SyncEngine({
nodeId: 'client-1',
connectionProvider: provider,
storageAdapter: new IDBAdapter(),
}); AutoConnectionProviderConfig
interface AutoConnectionProviderConfigParameters
urlstring. Server URL (ws:// or http://).clientIdstring. Client identifier.hlcHLC. Hybrid Logical Clock instance.maxWsAttempts?number. Maximum WebSocket connection attempts before falling back to HTTP. Default: 3authToken?string. JWT auth token.httpOnly?boolean. Skip WebSocket and use HTTP-only mode. Default: falsehttpPollIntervalMs?number. HTTP polling interval in milliseconds. Default: 5000syncMaps?string[]. Map names to sync via HTTP.fetchImpl?typeof fetch. Custom fetch implementation for HTTP mode.
HTTP Mode Limitations
HTTP sync mode supports batch operations and one-shot queries only. It does not support live query subscriptions,
Merkle tree synchronization, or real-time topic messages. For real-time features, use WebSocket mode or
AutoConnectionProvider
which will use WebSocket when available.
Core Methods
start()
await client.start(); Non-Blocking Initialization
The IDBAdapter initializes IndexedDB in the background.
You can start using the client immediately—write operations are queued in memory and persisted once IndexedDB is ready.
This provides true “memory-first” behavior with zero blocking time.
getMap<K, V>(name)
const users = client.getMap<string, User>('users');
// Write data
users.set('u1', { name: 'Alice' });
// Read data
const user = users.get('u1');
// Listen for changes
const unsubscribe = users.onChange(() => {
console.log('Users map changed');
}); getORMap<K, V>(name)
const tags = client.getORMap<string, string>('tags');
// Add values (multiple values per key allowed)
tags.add('post:123', 'javascript');
tags.add('post:123', 'typescript');
// Read all values for a key
const postTags = tags.get('post:123');
// Returns: ['javascript', 'typescript']
// Listen for changes
const unsubscribe = tags.onChange(() => {
console.log('Tags changed');
}); topic(name)
const chat = client.topic('chat-room');
// Publish a message
chat.publish({ text: 'Hello!' });
// Subscribe to messages (returns unsubscribe function)
const unsubscribe = chat.subscribe((msg) => {
console.log(msg);
});
// Later: stop listening
unsubscribe(); getLock(name)
const lock = client.getLock('resource-A');
if (await lock.lock()) {
// Critical section
await lock.unlock();
} query<T>(mapName, filter)
const handle = client.query<Todo>('todos', {
where: { completed: false },
sort: { createdAt: 'desc' },
limit: 10
});
const unsubscribe = handle.subscribe((results) => {
console.log('Results:', results);
});
// Cursor-based pagination (Phase 14.1)
const { nextCursor, hasMore } = handle.getPaginationInfo();
if (hasMore && nextCursor) {
// Fetch next page
const nextPage = client.query<Todo>('todos', {
where: { completed: false },
sort: { createdAt: 'desc' },
limit: 10,
cursor: nextCursor // Opaque cursor from previous page
});
} executeOnKey<V, R>(mapName, key, processor)
import { BuiltInProcessors } from '@topgunbuild/core';
// Atomic increment
const result = await client.executeOnKey(
'stats', // map name
'pageViews', // key
BuiltInProcessors.INCREMENT(1)
);
console.log(result.newValue); // New value after increment
console.log(result.success); // true executeOnKeys<V, R>(mapName, keys, processor)
import { BuiltInProcessors } from '@topgunbuild/core';
// Increment multiple counters
const results = await client.executeOnKeys(
'counters',
['views', 'clicks', 'shares'],
BuiltInProcessors.INCREMENT(1)
);
for (const [key, result] of results) {
console.log(`${key}: ${result.newValue}`);
} Entry Processor
Entry processors execute user-defined logic atomically on the server. This solves race conditions where multiple clients might read, modify, and write the same data concurrently. See the Entry Processor Guide for detailed usage.
close()
client.close(); Authentication
Use setAuthToken to authenticate the client. The token is sent to the server on connection and reconnection.
client.setAuthToken('jwt-token-here'); For dynamic token refresh (e.g., when tokens expire), use setAuthTokenProvider:
// Provider is called on each reconnection
client.setAuthTokenProvider(async () => {
const token = await refreshToken();
return token;
}); Unsubscribing from Changes
All subscription methods return an unsubscribe function. Call it to stop receiving updates:
// Data structure changes
const users = client.getMap<string, User>('users');
const unsubscribeMap = users.onChange(() => {
console.log('Users changed');
});
// Live query results
const query = client.query<Todo>('todos', { where: { done: false } });
const unsubscribeQuery = query.subscribe((results) => {
console.log('Query results:', results);
});
// Topic messages
const chat = client.topic('chat-room');
const unsubscribeTopic = chat.subscribe((msg) => {
console.log('New message:', msg);
});
// Later: clean up all subscriptions
unsubscribeMap();
unsubscribeQuery();
unsubscribeTopic(); Important: The onChange() callback receives no arguments—it only signals that something changed.
To see what changed, re-read the data inside the callback. For filtered results with automatic updates, use client.query() instead.
Cluster Failover
When a node becomes unavailable:
- Circuit breaker opens after consecutive failures (default: 5)
- Traffic reroutes to healthy nodes automatically
- Circuit resets after a timeout period (default: 30 seconds)
- Partition map updates when cluster topology changes
// Monitor connection state
client.onConnectionChange((state) => {
if (state === 'connected') {
console.log('Connected to cluster');
} else if (state === 'reconnecting') {
console.log('Reconnecting to cluster...');
}
});
// Operations continue working during failover
// The client automatically retries on healthy nodes
await users.set('key', { data: 'value' }); Automatic Recovery
The client maintains connections to multiple cluster nodes. If one node fails, operations are automatically rerouted to other available nodes. When the failed node recovers, the client will start using it again after the circuit breaker resets.