TypeScript client library for Theater actor system TCP protocol
npm install theater-clientA TypeScript client library for the Theater actor system with hygienic connection management.
- ๐งน Hygienic Connections - Each operation gets its own TCP connection to avoid response multiplexing
- ๐ก Protocol Fidelity - Types exactly match the Rust Theater server implementation
- โก Real-time Communication - Channel streams and actor event subscriptions
- ๐ก๏ธ Type Safety - Full TypeScript support with comprehensive error handling
- ๐ฏ Single Responsibility - Pure Theater protocol client with no domain logic
``bash`
npm install theater-clientor
bun add theater-client
`typescript
import { TheaterClient } from 'theater-client';
const client = new TheaterClient('127.0.0.1', 9000);
// Start an actor - returns Actor wrapper
const actor = await client.startActor({
manifest: '/path/to/manifest.toml',
initialState: new TextEncoder().encode(JSON.stringify({ config: 'value' }))
});
// Much cleaner API - no more passing actor ID everywhere!
await actor.sendJson({ type: 'command', data: 'value' });
const response = await actor.requestJson({ type: 'query' });
await actor.stop();
`
`typescript`
// Traditional approach - still supported
const actorId = await client.startActorRaw({ manifest: '/path/to/manifest.toml' });
await client.sendActorMessage(actorId, data);
const response = await client.requestActorMessage(actorId, request);
await client.stopActor(actorId);
The Actor class provides a clean, object-oriented interface around an actor ID.
`typescript`
export class Actor {
readonly id: TheaterId;
// Actor management
async getStatus(): Promise
async restart(): Promise
async stop(): Promise
async getManifest(): Promise
async getState(): Promise
async getEvents(): Promise
async getMetrics(): Promise
// Raw messaging (core methods)
async sendBytes(data: Uint8Array): Promise
async requestBytes(data: Uint8Array): Promise
// Convenience messaging
async sendJson(obj: any): Promise
async requestJson
async sendString(text: string): Promise
async requestString(text: string): Promise
// Real-time communication
async openChannel(initialMessage?: Uint8Array): Promise
async subscribe(): Promise
// Utility methods
toString(): string;
equals(other: Actor): boolean;
hasId(id: TheaterId): boolean;
}
#### Actor Examples
`typescript
const client = new TheaterClient();
// Start actor - returns Actor wrapper
const actor = await client.startActor({ manifest: '/path/to/manifest.toml' });
// Clean operations - no more actor ID repetition!
const status = await actor.getStatus();
await actor.sendJson({ type: 'hello', message: 'world' });
const response = await actor.requestJson({ type: 'ping' });
// Raw bytes when needed
await actor.sendBytes(new Uint8Array([1, 2, 3, 4]));
const rawResponse = await actor.requestBytes(someData);
// Real-time communication
const channel = await actor.openChannel();
const events = await actor.subscribe();
// Stop when done
await actor.stop();
`
#### Working with Existing Actors
`typescript
// Get Actor wrapper for existing ID
const existingActor = client.actor('actor-id-123');
await existingActor.sendJson({ type: 'command' });
// List all actors as Actor wrappers
const actors = await client.listActors();
for (const actor of actors) {
const status = await actor.getStatus();
console.log(Actor ${actor.id}: ${status});`
}
The main client class that provides all Theater operations with automatic connection management.
#### Constructor
`typescript`
new TheaterClient(host?: string, port?: number, config?: Partial
#### Actor Management
`typescript
// Start an actor and return Actor wrapper
async startActor(params: StartActorParams): Promise
// Start an actor and return raw ID
async startActorRaw(params: StartActorParams): Promise
// Get Actor wrapper for existing ID
actor(id: TheaterId): Actor
// List all actors as Actor wrappers
async listActors(): Promise
// List all actors as raw info
async listActorsRaw(): Promise
// Stop an actor
async stopActor(id: TheaterId): Promise
// List all actors
async listActors(): Promise
// Get actor status
async getActorStatus(id: TheaterId): Promise
// Restart an actor
async restartActor(id: TheaterId): Promise
// Get actor manifest
async getActorManifest(id: TheaterId): Promise
// Get actor state
async getActorState(id: TheaterId): Promise
// Get actor events
async getActorEvents(id: TheaterId): Promise
// Get actor metrics
async getActorMetrics(id: TheaterId): Promise
`
#### Messaging
`typescript
// Send fire-and-forget message
async sendActorMessage(id: TheaterId, data: Uint8Array): Promise
// Send request and wait for response
async requestActorMessage(id: TheaterId, data: Uint8Array): Promise
`
#### Real-time Communication
`typescript
// Open a channel for bidirectional communication
async openChannel(participant: ChannelParticipant, initialMessage?: Uint8Array): Promise
// Subscribe to actor events
async subscribeToActor(id: TheaterId): Promise
`
Real-time bidirectional communication with actors.
`typescript`
interface ChannelStream {
readonly channelId: string;
readonly isOpen: boolean;
// Event handling
onMessage(handler: (message: ChannelMessage) => void): () => void;
onClose(handler: () => void): () => void;
onError(handler: (error: Error) => void): () => void;
// Operations
sendMessage(data: Uint8Array): Promise
close(): void;
}
#### Example
`typescript
const channel = await client.openChannel({ Actor: actorId });
// Listen for messages
const unsubscribe = channel.onMessage((message) => {
const text = new TextDecoder().decode(message.data);
console.log(Received: ${text});
});
// Send messages
await channel.sendMessage(new TextEncoder().encode('Hello!'));
// Clean up
unsubscribe();
channel.close();
`
Subscribe to events from a specific actor.
`typescript`
interface ActorEventStream {
readonly actorId: string;
readonly subscriptionId: string;
readonly isActive: boolean;
// Event handling
onEvent(handler: (event: ChainEvent) => void): () => void;
onError(handler: (error: Error) => void): () => void;
onClose(handler: () => void): () => void;
// Operations
close(): void;
}
#### Example
`typescript
const eventStream = await client.subscribeToActor(actorId);
eventStream.onEvent((event) => {
console.log('Actor event:', event);
});
// Clean up when done
eventStream.close();
`
The library provides specific error types for different scenarios:
`typescript
import {
TheaterError,
TheaterConnectionError,
TheaterTimeoutError,
TheaterProtocolError
} from 'theater-client';
try {
await client.startActor({ manifest: '/invalid/path' });
} catch (error) {
if (error instanceof TheaterConnectionError) {
console.error('Connection failed:', error.message);
} else if (error instanceof TheaterTimeoutError) {
console.error('Operation timed out:', error.message);
} else if (error instanceof TheaterError) {
console.error('Theater error:', error.message, error.details);
}
}
`
`typescript`
const client = new TheaterClient('127.0.0.1', 9000, {
timeout: 30000, // 30 second timeout
retryAttempts: 3, // Retry failed operations 3 times
retryDelay: 1000 // 1 second delay between retries
});
Control logging output:
`typescript
import { setLogLevel } from 'theater-client';
// Set global log level
setLogLevel('debug'); // 'debug' | 'info' | 'warn' | 'error'
`
This library implements a "hygienic connection pattern" where each operation gets its own TCP connection. This provides several benefits:
- No response multiplexing - Each operation has a dedicated connection
- Simplified error handling - Errors are isolated to specific operations
- Automatic cleanup - Connections are automatically closed after operations
- Concurrency safety - Multiple operations can run safely in parallel
Types are designed to exactly match the Rust Theater server implementation:
- ManagementCommand and ManagementResponse enumsUint8Array
- Binary data handling with โ number[] conversion
- FragmentingCodec support for large messages
- Complete error type mapping
See the /examples directory for complete usage examples:
- basic-operations.ts - Actor management and messagingchannel-communication.ts
- - Real-time bidirectional communication event-subscription.ts
- - Actor event monitoring
`bashInstall dependencies
bun install
MIT