StatefulActor is the base class you extend. Subclass it, declare async methods, and they become RPC-callable from a stub. It holds this.ctx (see Actor Context) and this.env (your bindings).
Construction
You don’t construct actors yourself — the runtime does. The base constructor wiresthis.ctx and this.env, so a subclass that doesn’t need init logic can omit its constructor entirely.
ctx.blockConcurrencyWhile — see Actor Context.
Dispatch Rules
- Public methods are RPC-callable from a stub — declare them
async(every stub call is aPromiseon the caller’s side regardless). - Methods whose names start with
_are internal helpers and are not RPC-exposed — the runtime rejects the call. - The
_prefix is the only opt-out. TypeScript’sprivatekeyword is erased at runtime and does not stop remote calls; name internal helpers with a leading_. fetch(req),alarm(info),webSocket(ws, req), the constructor, and methods defined onStatefulActoritself (not your subclass) are special and not auto-RPC.- Methods have a wall-clock budget (30s by default). A call that exceeds it fails with
ActorMethodTimeoutError— see Errors.
alarm(alarmInfo)
Optional alarm handler. Override to handle scheduled work. The base implementation is a no-op.
fetch(req)
Optional HTTP-style entry. The base default returns 404. Override if you want callers to reach the actor over a raw Request instead of RPC methods:
env.BINDING.idFromName(name).fetch(req).
webSocket(ws, req)
Optional WebSocket entry. Unlike alarm() and fetch(), there is no default implementation — declaring the method is the opt-in. An actor that doesn’t declare webSocket() has no socket behavior at all, and an upgrade routed at it is closed with code 1011 (reason actor has no webSocket handler).
- Called once per accepted connection, with the live socket and the handshake
Request.reqis the request your function’s front door forwarded: the URL plus application headers, with transport and handshake headers stripped — see WebSockets for the contract. Client-sent app headers pass through too, so trust only headers your front door explicitly set or overwrote — the front door must overwrite or strip anything auth-bearing (likex-userabove) before forwarding. wsis a Nodewssocket (import type { WebSocket } from "ws"):ws.on("message", (data, isBinary) => ...),ws.send(data),ws.close(code, reason). There is noWebSocketPair/accept()shape.- Register your listeners before
webSocket()returns. Frames that arrive beforewebSocket()returns — including any the client sent immediately after the handshake — are buffered and replayed in order once it returns; nothing drops. - Socket events share the instance’s single-threaded dispatch.
message,close, anderrorhandlers run one at a time, serialized with RPC methods and alarms, each under the method wall-clock budget (30s by default). A message handler that throws or exceeds the budget closes the socket with code1011.
fetch front door: check the Upgrade header, authenticate, then forward with env.BINDING.idFromName(name).fetch(...). See WebSockets for the front door, connection lifetime, close codes, and delivery semantics.
Related
- WebSockets — the full connection contract behind
webSocket() - Actor Context —
this.ctx: identity, storage, init - Actor Storage —
this.ctx.storage - Configuration — the
telnyx.tomlactor block