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

  • serverUrl
    string. 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.
  • storage
    IStorageAdapter. 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 ClusterConfig

Parameters

  • seeds
    string[]. 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 HttpSyncProviderConfig

Parameters

  • url
    string. HTTP URL of the TopGun server (e.g., 'https://api.example.com').
  • clientId
    string. Client identifier for request construction.
  • hlc
    HLC. Hybrid Logical Clock instance for causality tracking.
  • authToken?
    string. JWT auth token for the Authorization header.
  • pollIntervalMs?
    number. Polling interval in milliseconds. Default: 5000
  • requestTimeoutMs?
    number. HTTP request timeout in milliseconds. Default: 30000
  • syncMaps?
    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 AutoConnectionProviderConfig

Parameters

  • url
    string. Server URL (ws:// or http://).
  • clientId
    string. Client identifier.
  • hlc
    HLC. Hybrid Logical Clock instance.
  • maxWsAttempts?
    number. Maximum WebSocket connection attempts before falling back to HTTP. Default: 3
  • authToken?
    string. JWT auth token.
  • httpOnly?
    boolean. Skip WebSocket and use HTTP-only mode. Default: false
  • httpPollIntervalMs?
    number. HTTP polling interval in milliseconds. Default: 5000
  • syncMaps?
    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()

Initializes the storage adapter and starts the synchronization engine.
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)

Returns an LWWMap (Last-Writer-Wins Map). Best for simple key-value data where the latest update wins.
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)

Returns an ORMap (Observed-Remove Map). Best for sets or lists where concurrent additions should be preserved.
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)

Returns a TopicHandle for real-time Pub/Sub messaging. Messages are ephemeral and not stored.
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)

Returns a DistributedLock handle. Useful for coordinating tasks across multiple clients.
const lock = client.getLock('resource-A');
if (await lock.lock()) {
  // Critical section
  await lock.unlock();
}

query<T>(mapName, filter)

Creates a live query subscription for a map. Returns a QueryHandle that can be subscribed to for real-time updates.
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)

Executes an Entry Processor atomically on a single key. Solves read-modify-write race conditions.
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)

Executes an Entry Processor on multiple keys. Returns a Map of results.
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()

Closes the client, disconnecting from the server and cleaning up resources.
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

In cluster mode, the client automatically handles node failures with built-in circuit breaker logic.

When a node becomes unavailable:

  1. Circuit breaker opens after consecutive failures (default: 5)
  2. Traffic reroutes to healthy nodes automatically
  3. Circuit resets after a timeout period (default: 30 seconds)
  4. 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.