Skip to content
English

Spawn typed

A Behavior<T> is a value. To put it into the runtime — to get an actual ActorRef<T> you can tell messages to — use the method on whichever host you’re spawning from:

WhereMethod
Outside an actorsystem.spawnTyped(behavior, name) / system.spawnTypedAnonymous(behavior)
Inside an untyped Actor.onReceivethis.context.spawnTyped(behavior, name) / this.context.spawnTypedAnonymous(behavior)
Inside a typed Behaviors.setup / handlercontext.spawn(behavior, name)
Anywhere an actor class or factory is acceptedtypedActor(behavior)

The method form mirrors the standard OO API — spawn vs. spawnAnonymous for whether you supply a name.

import { match } from 'ts-pattern';
import { ActorSystem, Behaviors, type Behavior, type ActorRef } from 'actor-ts';
type IncrementCommand = { kind: 'increment' };
type GetCommand = { kind: 'get'; replyTo: ActorRef<number> };
type Command = IncrementCommand | GetCommand;
const counter = (n: number): Behavior<Command> => Behaviors.receive((context, command) =>
match(command)
.with({ kind: 'increment' }, () => counter(n + 1))
.with({ kind: 'get' }, (c) => { c.replyTo.tell(n); return Behaviors.same; })
.exhaustive());
const system = ActorSystem.create('demo');
const ref = system.spawnTyped(counter(0), 'counter');
// ^- ActorRef<Command>

The signatures:

class ActorSystem {
spawnTyped<T>(behavior: Behavior<T>, name: string): ActorRef<T>;
spawnTypedAnonymous<T>(behavior: Behavior<T>): ActorRef<T>;
}

Returns a typed ActorRef<T>tell accepts only Command-shaped messages, the compiler enforces it.

ctx.spawnTyped — typed child from an untyped parent

Section titled “ctx.spawnTyped — typed child from an untyped parent”
import { Actor, Behaviors } from 'actor-ts';
class UntypedParent extends Actor<...> {
override preStart(): void {
const typedChild = this.context.spawnTyped(counter(0), 'child');
// typedChild: ActorRef<Command>
typedChild.tell({ kind: 'increment' });
}
}

The signatures live on ActorContext:

interface ActorContext {
spawnTyped<T>(behavior: Behavior<T>, name: string): ActorRef<T>;
spawnTypedAnonymous<T>(behavior: Behavior<T>): ActorRef<T>;
}

Useful when you have an existing untyped supervisor that needs to spawn typed workers. The child is a normal entry in the parent’s children list — supervisor strategies apply per the parent’s strategy, death watch works both ways.

typedActor — interop with the class-or-factory API

Section titled “typedActor — interop with the class-or-factory API”
import { ActorOptions, typedActor } from 'actor-ts';
const counterActor = typedActor(counter(0));
const counterOptions = ActorOptions.create<Command>()
.withMailboxCapacity(500)
.withDispatcher(myDispatcher);
const ref = system.spawn(counterActor, 'counter', counterOptions);

When you want a typed Behavior but the API takes an actor — an entry point that predates the typed layer, or a spawn that needs ActorOptions of its own — typedActor(behavior) returns the ActorFactory<T> those places accept.

The shape:

function typedActor<T>(behavior: Behavior<T>): ActorFactory<T>;

The returned factory can be passed anywhere an actor is expected — system.spawn, context.spawn, ClusterSingleton.start, the sharding region’s entityActor slot.

Inside a typed handler, the context exposes its own spawn:

const parent: Behavior<ParentMessage> = Behaviors.setup((context) => {
const child = context.spawn(workerBehavior, 'worker');
// ^- ActorRef<WorkerMessage>
return Behaviors.receive((context, message) => {
child.tell({ kind: 'do-it' });
return Behaviors.same;
});
});

This is the standard way for typed parents to spawn typed children — typed all the way through. context.spawn(behavior) knows the child’s message type from the Behavior’s type parameter.

For the deterministic variants (spawnTyped), the name parameter is required and must be unique among siblings. For spawnTypedAnonymous, the framework generates one — $anonymous-, a per-parent counter and twelve random hex characters, for example '$anonymous-1-3f9c1a0d7b42'. The suffix is random, so the path is not stable across runs; use spawnTyped whenever you need to address the actor by path.

The resulting actor’s path follows the standard format:

  • system.spawnTyped(b, 'counter')actor-ts://my-app/user/counter
  • parentContext.spawnTyped(b, 'worker')actor-ts://my-app/user/<parent>/worker
  • context.spawn(b, 'worker')actor-ts://my-app/user/<parent>/worker

See Actor paths for the path semantics.

Caller is outside an actor:
→ system.spawnTyped(b, name) // with a name
→ system.spawnTypedAnonymous(b) // throwaway
Caller is inside an untyped Actor.onReceive:
→ this.context.spawnTyped(b, name)
→ this.context.spawnTypedAnonymous(b)
Caller is inside a typed Behaviors.setup or handler:
→ context.spawn(b, name)
You have a function that takes an actor and you want to use a Behavior:
→ typedActor(b)

The methods are deliberately small — each handles one common case. typedActor is the escape hatch when you need to plug a behavior into actor-shaped APIs.

  • Behaviors — the DSL that produces the values you pass to these methods.
  • Typed actor — the runtime the spawn methods wrap.
  • Spawning actors — the untyped configuration typedActor returns.
  • Actor system — the spawn API the methods ultimately call.