UNPKG

theater-client

Version:

TypeScript client library for Theater actor system TCP protocol

370 lines (267 loc) โ€ข 9.49 kB
# 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 ```bash npm install theater-client # or bun add theater-client ``` ## Quick Start ### Using the Actor Wrapper (Recommended) ```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(); ``` ### Using Raw Client (Also Available) ```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); ``` ## API Reference ### Actor Wrapper (Recommended) 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<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 ```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}`); } ``` ### TheaterClient The main client class that provides all Theater operations with automatic connection management. #### Constructor ```typescript new TheaterClient(host?: string, port?: number, config?: Partial<TheaterClientConfig>) ``` #### Actor Management ```typescript // 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 ```typescript // 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 ```typescript // 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. ```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<void>; 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(); ``` ### ActorEventStream 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(); ``` ## Error Handling 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); } } ``` ## Configuration ```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 }); ``` ## Logging Control logging output: ```typescript 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: - `ManagementCommand` and `ManagementResponse` enums - 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 messaging - `channel-communication.ts` - Real-time bidirectional communication - `event-subscription.ts` - Actor event monitoring ## Development ```bash # Install dependencies bun install # Build the library bun run build # Run tests bun test # Watch mode during development bun run build:watch ``` ## License MIT