theater-client
v0.1.1
Published
TypeScript client library for Theater actor system TCP protocol
Downloads
8
Maintainers
Readme
Theater Client
A TypeScript client library for the Theater actor system with hygienic connection management.
Features
- 🧹 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
Installation
npm install theater-client
# or
bun add theater-clientQuick Start
Using the Actor Wrapper (Recommended)
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();Using Raw Client (Also Available)
// 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);API Reference
Actor Wrapper (Recommended)
The Actor class provides a clean, object-oriented interface around an actor ID.
export class Actor {
readonly id: TheaterId;
// Actor management
async getStatus(): Promise<ActorStatus>;
async restart(): Promise<void>;
async stop(): Promise<void>;
async getManifest(): Promise<ManifestConfig>;
async getState(): Promise<Uint8Array | null>;
async getEvents(): Promise<ChainEvent[]>;
async getMetrics(): Promise<any>;
// Raw messaging (core methods)
async sendBytes(data: Uint8Array): Promise<void>;
async requestBytes(data: Uint8Array): Promise<Uint8Array>;
// Convenience messaging
async sendJson(obj: any): Promise<void>;
async requestJson<T>(obj: any): Promise<T>;
async sendString(text: string): Promise<void>;
async requestString(text: string): Promise<string>;
// Real-time communication
async openChannel(initialMessage?: Uint8Array): Promise<ChannelStream>;
async subscribe(): Promise<ActorEventStream>;
// Utility methods
toString(): string;
equals(other: Actor): boolean;
hasId(id: TheaterId): boolean;
}Actor Examples
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
// 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}`);
}TheaterClient
The main client class that provides all Theater operations with automatic connection management.
Constructor
new TheaterClient(host?: string, port?: number, config?: Partial<TheaterClientConfig>)Actor Management
// Start an actor and return Actor wrapper
async startActor(params: StartActorParams): Promise<Actor>
// Start an actor and return raw ID
async startActorRaw(params: StartActorParams): Promise<TheaterId>
// Get Actor wrapper for existing ID
actor(id: TheaterId): Actor
// List all actors as Actor wrappers
async listActors(): Promise<Actor[]>
// List all actors as raw info
async listActorsRaw(): Promise<ActorInfo[]>
// Stop an actor
async stopActor(id: TheaterId): Promise<void>
// List all actors
async listActors(): Promise<ActorInfo[]>
// Get actor status
async getActorStatus(id: TheaterId): Promise<ActorStatus>
// Restart an actor
async restartActor(id: TheaterId): Promise<void>
// Get actor manifest
async getActorManifest(id: TheaterId): Promise<ManifestConfig>
// Get actor state
async getActorState(id: TheaterId): Promise<Uint8Array | null>
// Get actor events
async getActorEvents(id: TheaterId): Promise<ChainEvent[]>
// Get actor metrics
async getActorMetrics(id: TheaterId): Promise<any>Messaging
// Send fire-and-forget message
async sendActorMessage(id: TheaterId, data: Uint8Array): Promise<void>
// Send request and wait for response
async requestActorMessage(id: TheaterId, data: Uint8Array): Promise<Uint8Array>Real-time Communication
// Open a channel for bidirectional communication
async openChannel(participant: ChannelParticipant, initialMessage?: Uint8Array): Promise<ChannelStream>
// Subscribe to actor events
async subscribeToActor(id: TheaterId): Promise<ActorEventStream>ChannelStream
Real-time bidirectional communication with actors.
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<void>;
close(): void;
}Example
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();ActorEventStream
Subscribe to events from a specific actor.
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
const eventStream = await client.subscribeToActor(actorId);
eventStream.onEvent((event) => {
console.log('Actor event:', event);
});
// Clean up when done
eventStream.close();Error Handling
The library provides specific error types for different scenarios:
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);
}
}Configuration
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
});Logging
Control logging output:
import { setLogLevel } from 'theater-client';
// Set global log level
setLogLevel('debug'); // 'debug' | 'info' | 'warn' | 'error'Architecture
Hygienic Connection Pattern
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
Protocol Compatibility
Types are designed to exactly match the Rust Theater server implementation:
ManagementCommandandManagementResponseenums- Binary data handling with
Uint8Array↔number[]conversion - FragmentingCodec support for large messages
- Complete error type mapping
Examples
See the /examples directory for complete usage examples:
basic-operations.ts- Actor management and messagingchannel-communication.ts- Real-time bidirectional communicationevent-subscription.ts- Actor event monitoring
Development
# Install dependencies
bun install
# Build the library
bun run build
# Run tests
bun test
# Watch mode during development
bun run build:watchLicense
MIT
