Node.js Actor And Session Binding Public Interface¶
Interface table of contents · Actor Model · Spot/Actor Membership
Session–Actor binding §8.2 owns relocation route updates for a bound Session.
This document fixes the TypeScript declarations related to Actor
that @zlink-systems/framework and @zlink-systems/nestjs export in
ZLink Framework.
1. Actor Identity, Factory, And Context¶
ActorId is a logical ID unique across the whole Location Store
transaction domain. Its UTF-8 encoded size is 1..255 bytes, it's
compared as a case-sensitive value comparison, and it isn't normalized. A
regular message only takes ActorId and resolves current authority.
ActorRef is the immutable location snapshot used to change an
incarnation or bind to a session.
export interface ZLinkActor {
readonly context: ZLinkActorContext;
configure?(): void;
onJoinCompleted?(completion: ZLinkActorJoinCompletion): Promise<void>;
}
export interface ZLinkActorContext {
readonly actorId: ActorId;
readonly objectGeneration: bigint;
readonly meshName: string;
readonly spotId?: SpotId;
readonly boundSession: ZLinkBoundSession;
joinSpot(spotId: SpotId): ZLinkActorJoinSpotCall;
joinSpot(spotId: SpotId, request: unknown): ZLinkActorJoinSpotCall;
joinEntrySpot(): ZLinkActorJoinEntrySpotCall;
joinEntrySpot(request: unknown): ZLinkActorJoinEntrySpotCall;
}
export interface ZLinkActorFactory<TActor extends ZLinkActor = ZLinkActor> {
create(context: ZLinkActorContext, signal?: AbortSignal): Promise<TActor>;
}
export interface ZLinkActorHandlerRegistry {
addHandler<THandler>(handlerType: Type<THandler>, packetName?: string): this;
}
export interface ZLinkActorJoinCall<TSelf> {
timeout(timeoutMs: number): TSelf;
defer(): void;
}
export interface ZLinkActorJoinEntrySpotCall
extends ZLinkActorJoinCall<ZLinkActorJoinEntrySpotCall> {}
export interface ZLinkActorJoinSpotCall
extends ZLinkActorJoinCall<ZLinkActorJoinSpotCall> {}
export interface ZLinkActorJoinOperationId {
readonly high: bigint;
readonly low: bigint;
}
export type ZLinkActorJoinCompletion =
| { readonly status: 'accepted'; readonly operationId: ZLinkActorJoinOperationId;
readonly actor: ActorRef; readonly reply?: ZLinkMessage }
| { readonly status: 'rejected'; readonly operationId: ZLinkActorJoinOperationId;
readonly reply?: ZLinkMessage }
| { readonly status: 'failed'; readonly operationId: ZLinkActorJoinOperationId;
readonly kind: ZLinkFrameworkErrorKind };
The canonical declaration of ActorId and ActorRef is owned by
Foundation Types And Configuration.
This document only fixes the location where the Actor lifecycle
and manager use that type.
2. Global Client And Manager¶
export interface ZLinkActorClient {
sendToActor(actorId: ActorId, message: unknown): ZLinkActorSendCall;
requestToActor(actorId: ActorId, request: unknown): ZLinkActorRequestCall;
}
export interface ZLinkActorManager {
create(actorId: ActorId, actorType: string): ZLinkActorCreateCall;
getOrCreate(actorId: ActorId, actorType: string): ZLinkActorGetOrCreateCall;
find(actorId: ActorId, signal?: AbortSignal): Promise<ActorRef | undefined>;
findSpot(actorId: ActorId, signal?: AbortSignal): Promise<SpotRef | undefined>;
destroy(actor: ActorRef, signal?: AbortSignal): Promise<boolean>;
}
export interface ZLinkActorCreateCall {
inMesh(meshName: string): this;
request(request: unknown): this;
timeout(timeoutMs: number): this;
submit(signal?: AbortSignal): Promise<ZLinkActorCreateResult>;
yield(signal?: AbortSignal): Promise<ZLinkActorCreateResult>;
}
export interface ZLinkActorGetOrCreateCall {
inMesh(meshName: string): this;
request(request: unknown): this;
timeout(timeoutMs: number): this;
submit(signal?: AbortSignal): Promise<ZLinkActorCreateResult>;
yield(signal?: AbortSignal): Promise<ZLinkActorCreateResult>;
}
export type ZLinkActorCreateResult =
| { readonly status: 'existing'; readonly actor: ActorRef }
| {
readonly status: 'created';
readonly actor: ActorRef;
readonly reply?: unknown;
}
| { readonly status: 'rejected'; readonly reply?: unknown };
export interface ZLinkActorRequestCall {
metadata(key: string, value: string): this;
timeout(timeoutMs: number): this;
submit<TReply>(signal?: AbortSignal): Promise<TReply>;
yield<TReply>(signal?: AbortSignal): Promise<TReply>;
}
export interface ZLinkActorSendCall {
metadata(key: string, value: string): this;
submit(signal?: AbortSignal): Promise<void>;
}
Actor Model §6.2 owns the single-use rules, duplicate-option handling, and terminal
re-invocation errors of create and getOrCreate calls.
If inMesh(...) is omitted and there are two or
more eligible Meshes, InvalidOperation; if there's no object-role Mesh
at all, NotConfigured. If the specified Mesh doesn't exist, NotFound.
The caller doesn't specify a target RID or predicate.
create returns AlreadyExists if a ready incarnation of the same
ActorId exists, and TypeMismatch if stable type differs. A new attempt
returns created or rejected. getOrCreate returns a
ready Actor of the same type as
existing, without a callback. If Creating, it waits for the authority
change, and a CAS loser doesn't start a separate factory or callback. A
different operation receives existing after ready, competes for a new
reservation after cleanup, and doesn't share an earlier application
reply. Only a resend of the same source Node RID/lifecycle
generation/OperationId reads the correlation-free
creation-operation-terminal-v1 envelope and re-encodes the reply with
the current correlation/reply route. The terminal is kept for 5 minutes
after the original deadline. A callback exception isn't rejected —
it's a typed creation failure. If the whole deadline ends,
DeadlineExceeded; if there's no capacity, CapacityExceeded. An
lifecycle operation whose ActorRef's object generation differs from
current is InvalidOperation, and Unavailable while moving.
Actor create finishes the selected owner MeshNode's Entry Spot membership and the Ready barrier in the same lifecycle. After Ready, a one-way message is submitted directly to the Actor queue. Even if a stale route is confirmed after resolve or queue admission, the framework doesn't find a new owner and hidden-retry the same operation.
The Actor Join call only provides a synchronous defer(), and doesn't
provide submit(...)/yield(...). defer() only registers an
immutable Join intent and an inactive barrier on the current handler,
and doesn't start a target lookup or Store I/O. If the handler finishes
normally, the Join runs; if it fails, the barrier is discarded. If the
handler used yield(...), the barrier isn't activated until the last
continuation finishes.
The result is delivered via the onJoinCompleted(...) Actor callback
with the same operation ID. Operation ID is a completion idempotency
ID, not a RelocationId, reservation ID, or aggregate commit ID.
Same-node and cross-node completion retry are limited to the current
source and target process lifetime. After the process ends, a different
runtime doesn't automatically replay completion.
The overload with no request fixes an empty ZLinkMessage. The default
timeout is 5 seconds, and an explicit value is a finite
1..2_147_483_647 ms rounded up to milliseconds. The monotonic
absolute deadline is fixed at the moment defer() is called.
If ZLinkActorContext.spotId is absent, the Actor is a current
Entry Spot
member; if it has a value, it's a member of that User Spot. A separate
boolean or mutable Spot instance representing the same state isn't
provided. findSpot(actorId) also only returns the current User
Spot
membership as SpotRef,
and undefined on Entry Spot. The factory creates a new Actor and
context per target attempt, and doesn't reuse an instance whose
cross-node restore failed in the next attempt.
3. Session Binding¶
Session binding fixes the specified incarnation of ActorRef.actorId +
objectGeneration once. A bind overload taking a local Actor instance,
a global Actor directory, a handle resolver, and a separate ActorRef
snapshot conversion API aren't
provided. find(actorId) only queries an Actor already bound to that
session, not a global directory.
boundSession's push is a one-way operation only sent to the
connection the current binding token specifies. If the connection is
replaced or the binding generation changes, the previous operation
isn't retargeted to the new connection or hidden-retried. Disconnect
only releases the binding — Actor and Spot membership are kept.
The public trace category is actor-relocation. The meaning and
verification criteria are owned by
Actor Model,
Spot/Actor Membership, and
Session Actor Dispatch.
yield(...) declared on an Actor request is only valid while the
current Actor handler is running on a SpotWide User Spot's shared
execution gate. If called by an Entry Spot Actor or a PerActor User
Spot's Actor, it completes with invalidConfiguration, without
submitting the operation or returning the turn. Actor Join only
provides synchronous defer(), and doesn't provide submit(...) and
yield(...).