Skip to content

Your first actor

We'll build the smallest useful actor — a counter — and drive it. Every code block on this page is type-checked against the real @actor-web/runtime types when the site builds, so what you see compiles.

1. Define the behavior

An actor's behavior is authored with defineBehavior. You declare the message union it accepts, give it an initial context, and handle each message by returning the next context.

ts
import { 
defineBehavior
} from '@actor-web/runtime';
// The messages this actor accepts. type
CounterMessage
= {
type
: 'INCREMENT' } | {
type
: 'DECREMENT' };
export const
counter
=
defineBehavior
<
CounterMessage
>()
.
withContext
({
count
: 0 })
.
onMessage
(({
message
,
context
}) => {
switch (
message
.
type
) {
case 'INCREMENT': return {
context
: {
count
:
context
.
count
+ 1 } };
case 'DECREMENT': return {
context
: {
count
:
context
.
count
- 1 } };
} }) .
build
();

A few things to notice:

  • The handler receives { message, context, actor, tools }. Here we only need message and the current context.
  • Returning { context } replaces the actor's state — the OTP gen_server pattern. Return nothing to leave state unchanged.
  • count is inferred as number from withContext({ count: 0 }), so context.count + 1 is checked. Try changing it to a string and the build fails.

2. What a handler can return

A handler returns an ActorHandlerResult, any of these fields:

  • context — replace this actor's state.
  • reply — respond to an ask(...) caller (1-to-1).
  • emit — broadcast domain events to subscribers (1-to-many). This is how other actors react to you — see Subscriptions & events.
ts
type 
CounterMessage
= {
type
: 'INCREMENT' } | {
type
: 'DECREMENT' };
const
counter
=
defineBehavior
<
CounterMessage
>()
.
withContext
({
count
: 0 })
.
onMessage
(({
message
,
context
}) => {
if (
message
.
type
=== 'INCREMENT') {
const
count
=
context
.
count
+ 1;
// Update state AND announce the change as a fact. return {
context
: {
count
},
emit
: [{
type
: 'COUNT_CHANGED',
count
}] };
} return {
context
: {
count
:
context
.
count
- 1 } };
}) .
build
();

Next steps

  • Subscriptions & events — let another actor react to COUNT_CHANGED.
  • More to come: wiring a topology, running a local runtime, and consuming an actor from a UI.