API Reference
Everything defineActor accepts, in one place. See Quick Start for
the shortest working example.
defineActor
import { defineActor, defineMessages } from "posipaki";
const actor = defineActor(config);
defineActor(config) compiles a declarative config object into an
ActorDefinition with a .spawn() method. The config describes the actor's
initial state, message handlers, helper methods and lifecycle hooks.
Config fields
name
Type: string · optional
Preferred process name. Used by this.fork() when no explicit child name is
given.
inMessages / outMessages
Type: defineMessages<T>() · optional
Type tags for the actor's message contracts. Hand them a discriminated union
on type:
type MPoke = { type: "POKE" };
type MPing = { type: "PING"; step: number };
type MTick = { type: "TICK"; count: number };
const counter = defineActor({
inMessages: defineMessages<MPoke | MPing>(),
outMessages: defineMessages<MTick>(),
// ...
});
inMessages drives the narrowing of msg in handlers and the accepted
argument of proc.send(...). outMessages types this.emit(...) and the
parent's view of this actor's messages. Without them, messages fall back to
the loose Message type.
setup
Type: (args) => State | Promise<State> · optional
Returns the initial state. this.state is not available here — build the
state from args (and this.name / this.id if needed). Runs once, after
beforeStart and before afterStart. If omitted, the actor starts with
null state.
setup(args) {
return { count: 0, max: args.max };
},
handlers
Type: { [type]: (msg, sender) => void | Promise<void> } · required
The message dispatch table. The key is a message type; msg is narrowed to
the matching union member and sender is a SenderInfo. Handlers run one at
a time — an incoming message is buffered until the current handler finishes. Handlers can be async.
handlers: {
POKE(msg, sender) {
// msg: { type: "POKE" }
this.state.count++;
},
async PING(msg) {
// msg: { type: "PING"; step: number }
this.state.count += msg.step;
},
},
If a message has no handler, onUnhandled is called instead.
The special STOP type is handled automatically and is implied to be part of in-messages type.
See onStopRequested() in lifecycle section to learn more about it.
methods
Type: { [name]: (...args) => unknown } · optional
Helper methods merged onto this, so handlers can share logic without
repeating it:
methods: {
increment() { this.state.count++; },
beDone() {
this.emit({ type: "TICK", count: this.state.count });
this.exit("max reached");
},
},
handlers: {
POKE() { this.increment(); },
},
plugins
Type: ActorPlugin[] | (parents) => ActorPlugin[] · optional
Reusable units of actor behaviour that transform the config before assembly. See Plugins. Plugins are inherited by all children by default. When child actor defines their plugins as an array, they will be added to the list of plugins passed by parent and deduplicated on name.
When defined as function, the deicision to inherit or discard parent's plugins is deffered to the function.
Lifecycle hooks
Hooks are additive, when defined by plugins and actor itself, several can register for the same point and they fire in
registration order. Any hook may return stopPropagation() to skip the rest
of the chain. Hooks fire in this order:
beforeStart → setup → afterStart → [message loop] → beforeEnd → afterEnd
See Lifecycle for more in-depth explaination of process lifecycle.
beforeStart / afterStart
Type: () => void | Promise<void>
beforeStart fires once this is built but before setup (this.state is
never). Use it to register the process before forking any child. afterStart
fires after setup, with this.state available and after proc.read() resolved.
onStopRequested
Type: () => HookResult | Promise<HookResult>
Fired when a STOP arrives. The hook may call this.agreeToStop() to accept
it; if it doesn't, the actor keeps running. When no onStopRequested is
defined, a STOP stops the actor immediately. In either case no messages, including ones already in the mailbox will be processed.
beforeEnd / afterEnd
Type: (reason?) => HookResult | Promise<HookResult>
beforeEnd fires when the message loop finishes, with the exit reason.
afterEnd fires after that, as the process tears down.
onError
Type: (error?) => HookResult | Promise<HookResult>
Fired when a handler or hook throws, with the error.
onEmit / onMessage
Type: (msg, sender) => HookResult | Promise<HookResult>
onEmit observes every message the actor sends to its parent. onMessage
observes every incoming message before dispatch; returning stopPropagation()
skips the handler.
onUnhandled
Type: (msg, sender) => void | Promise<void>
Fired when a message arrives with no matching handler.
onChildExit
Type: (name, reason) => HookResult | Promise<HookResult>
Fired when a forked child exits. name is the child's tree-prefixed process
name (parent:child); reason is its ExitMessage.
onOrphan
Type: (orphan) => OrphanDecision | void
Fired for each orphan a child leaves behind in its EXIT. Return a policy:
"adopt"— promote to a child and drain its buffer"force-stop"— hard-kill it (the default whenonOrphanis absent)"unparent"— drop its buffer, keep it running inorphans"leave"— keep buffering, propagate it up on my exit
See Lifecycle page to learn more about orphan processes.
The handler context (this)
Inside handlers, methods and every hook, this is an ActorContext:
state— the current state, typed fromsetup's return.name/id— process name (string) and id (symbol).emit(msg)— send an out-message to the parent.exit(reason?)— terminate immediately with an optional reason.agreeToStop()— accept a pendingSTOP.fork(actor, args?, opts?)— spawn a child actor; returns its process handle. Children are tracked in$child.ctx— the underlying low-levelProcessCtx(sendSelf,toParent, and friends).reflection— reflection methods exposed by plugins.
Spawning
const proc = await counter.spawn(args, { name?, toParent?, addPlugins? });
await proc.ready();
proc.send({ type: "POKE" });
spawn returns an AsyncProcess handle:
ready()— resolves once the initial state is available.send(msg)— queue a message for the actor.state— the live state.subscribe("state" | "message", cb)— observe state changes or out-messages; returns an unsubscribe function.pause()/resume()— pause and resume message processing.wait()— resolves when the actor exits.
spawnAsChild(ctx, args, opts) spawns the actor as a child of an existing
process instead of a standalone root.
Types
Message—{ type: string }, the base every message extends.SenderInfo—{ fromName: string; fromId: symbol }.ExitMessage—{ type: "EXIT"; orphans?: AnyProcess[] }.OrphanDecision—"adopt" | "force-stop" | "unparent" | "leave".HandlerFn<InMsg>—(msg, sender) => void | Promise<void>.stopPropagation()— sentinel a hook returns to skip later hooks.