Skip to content

VaughnVernon/DomoActors-TS

Repository files navigation

DomoActors

Actor Model toolkit for TypeScript: Fault-tolerant, message-driven concurrency.

License: RPL-1.5 TypeScript npm version V8 Runtimes npm downloads GitHub stars

Overview

DomoActors provides a robust implementation of the Actor Model for TypeScript, enabling you to build concurrent, distributed, and fault-tolerant applications. Built on proven patterns from Reactive Architecture and Domain-Driven Design, DomoActors brings the power of message-driven actors to the TypeScript ecosystem.

This work is based on the Java version of XOOM/Actors, which I began experimenting on in 2012 and released into open source in 2016. The Java-based product burgeoned into a complete reactive platform.

Features

  • Core Actor Model: Full-featured actor implementation with lifecycle management
  • Fault Tolerance: Hierarchical supervision with configurable strategies (Resume, Restart, Stop, Escalate)
  • Flexible Messaging: Multiple mailbox implementations (ArrayMailbox, BoundedMailbox with overflow policies)
  • Type Safety: Full TypeScript support with Protocol-based type-safe messaging
  • Value Registry: Register and share runtime objects (databases, configuration, services) across all actors
  • Testing Support: Comprehensive TestKit with utilities for asynchronous testing
  • Observable State: Built-in state management with reactive updates
  • Scheduling: Task scheduling with cancellation support
  • Dead Letters: Dead letter handling for undeliverable messages
  • Child Actors: Hierarchical actor creation and management
  • Clean API: Intuitive, well-documented API designed for developer productivity

Installation

npm install domo-actors

Quick Start

import { Actor, Protocol, stage } from 'domo-actors'

// Define your actor's protocol
interface Counter {
  increment(): void
  decrement(): void
  getValue(): void
}

// Implement your actor
class CounterActor extends Actor implements Counter {
  private count = 0

  increment(): void {
    this.count++
    console.log(`Count: ${this.count}`)
  }

  decrement(): void {
    this.count--
    console.log(`Count: ${this.count}`)
  }

  getValue(): void {
    console.log(`Current count: ${this.count}`)
  }
}

// Create and use the actor
const counter = stage().actorFor<Counter>(Protocol.typed<Counter>(), CounterActor)

// actorFor() does two things:
//
//   1. actor instance is created and started
//   2. proxy instance is created and returned to caller

counter.increment()  // Count: 1
counter.increment()  // Count: 2
counter.getValue()   // Current count: 2

Supervision Example

import {
  Actor, ActorProtocol, Protocol, stage,
  SupervisionStrategy, SupervisionDirective
} from 'domo-actors'

interface Worker extends ActorProtocol {
  process(data: string): void
}

class WorkerActor extends Actor implements Worker {
  process(data: string): void {
    if (data === 'error') {
      throw new Error('Processing failed')
    }
    console.log(`Processed: ${data}`)
  }
}

// Custom supervision strategy
class WorkerSupervisor implements SupervisionStrategy {
  decide(error: Error): SupervisionDirective {
    console.log(`Supervisor handling error: ${error.message}`)
    return SupervisionDirective.Restart  // Restart the failed actor
  }
}

stage().registerSupervisor('worker-supervisor', new WorkerSupervisor())

const worker = stage().actorFor<Worker>(
  Protocol.typed<Worker>(),
  Worker,
  { supervisorName: 'worker-supervisor' }
)

worker.process('valid')  // Processed: valid
worker.process('error')  // Supervisor handles error and restarts actor

Stage Value Registry

The Stage Value Registry allows you to register and share runtime objects (like database connections, configuration, or service instances) across all actors in your application:

import { Actor, stage } from 'domo-actors'

// Register shared resources at application startup
const database = new DatabaseConnection('postgresql://localhost:5432/mydb')
const config = { apiKey: 'secret', timeout: 5000 }

stage().registerValue('myapp:database', database)
stage().registerValue('myapp:config', config)

// Access registered values from any actor
class UserRepositoryActor extends Actor {
  async findUser(id: string) {
    const db = this.stage().registeredValue<DatabaseConnection>('myapp:database')
    return await db.query('SELECT * FROM users WHERE id = ?', [id])
  }
}

class ApiClientActor extends Actor {
  async makeRequest(endpoint: string) {
    const config = this.stage().registeredValue<Config>('myapp:config')
    // Use config.apiKey and config.timeout
  }
}

// Clean up resources when no longer needed
const db = stage().deregisterValue<DatabaseConnection>('myapp:database')
if (db) {
  await db.close()  // Perform cleanup
}

Benefits:

  • Dependency Injection: Share dependencies without coupling actors to specific implementations
  • Configuration Management: Centralized configuration accessible from any actor
  • Resource Pooling: Share connection pools, caches, and other expensive resources
  • Type Safety: Full TypeScript type inference with generics

Best Practices:

  • Use namespaced keys to avoid conflicts (e.g., 'myapp:database', 'myapp:config')
  • Register values at application startup before creating actors
  • Use TypeScript generics for type-safe retrieval
  • Use deregisterValue() to clean up resources when they're no longer needed
  • Be cautious when deregistering - ensure no actors are still using the value

Testing

DomoActors includes comprehensive testing utilities:

import { awaitObservableState, awaitStateValue } from 'domo-actors'

// Wait for observable state changes
await awaitObservableState(observableState, state => state.count === 5)

// Wait for specific state values with timeout
await awaitStateValue(observableState, state => state.status, 'completed', {
  timeout: 5000,
  interval: 100
})

API Documentation

Full API documentation is available at: API Reference

Generate documentation locally:

npm run docs
npm run docs:serve

Development

Setting Up for Local Development

When developing DomoActors or running examples from the repository:

# Clone the repository
git clone https://github.com/VaughnVernon/DomoActors-TS.git
cd DomoActors-TS

# Install dependencies
npm install

# Set up local package linking (required for examples)
npm run install:local

# Build the library
npm run build

# Run tests
npm test

# Run examples
npm run example:bank
npm run example:encapsulation

Note: The npm run install:local command is required to link the local domo-actors package so that examples can import it. This creates symlinks in node_modules/ that point to your development build.

Uninstalling Local Links

If you need to remove the local package links:

npm run uninstall:local

Requirements

  • Runtimes: Node.js >= 18.0.0, Deno, Bun, Cloudflare Workers, or any V8-based JavaScript runtime
  • TypeScript: >= 5.0.0 (for development)

DomoActors has zero Node.js-specific dependencies and runs on any V8-compatible runtime.

Documentation

Contributing

DomoActors is authored by Vaughn Vernon and maintained as part of the Domo product family.

For issues and feature requests, please visit: https://github.com/VaughnVernon/DomoActors-TS/issues

License

Reciprocal Public License 1.5

See: ./LICENSE.md

Copyright © 2012-2025 Vaughn Vernon. All rights reserved. Copyright © 2012-2025 Kalele, Inc. All rights reserved.

About the Creator and Author

Vaughn Vernon

About

Actor Model toolkit for TypeScript: Fault-tolerant, message-driven concurrency.

Resources

License

Stars

Watchers

Forks

Packages

No packages published