npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

theater-client

v0.1.1

Published

TypeScript client library for Theater actor system TCP protocol

Downloads

8

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-client

Quick 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:

  • ManagementCommand and ManagementResponse enums
  • Binary data handling with Uint8Arraynumber[] 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

# Install dependencies
bun install

# Build the library
bun run build

# Run tests
bun test

# Watch mode during development
bun run build:watch

License

MIT