# 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
