Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion scripts/mintlify-post-processing/appended-articles.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"interfaces/ConnectorApiResponse",
"type-aliases/ConnectorApiResponsePhase"
],
"interfaces/AppModule": ["interfaces/AppPublicSettingsResponse"],
"interfaces/AppModule": [
"interfaces/AppPublicSettingsResponse"
],
"type-aliases/EntitiesModule": [
"interfaces/EntityHandler",
"type-aliases/EntityRecord",
Expand All @@ -25,5 +27,11 @@
"type-aliases/integrations": [
"interfaces/CoreIntegrations",
"interfaces/CustomIntegrationsModule"
],
"type-aliases/ActorsModule": [
"interfaces/ActorRef",
"interfaces/Connection",
"interfaces/ActorConnectOptions",
"interfaces/ActorRegistry"
]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[
"actors",
"AiGatewayConnection",
"DeleteManyResult",
"DeleteResult",
Expand Down
7 changes: 6 additions & 1 deletion scripts/mintlify-post-processing/types-to-expose.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
[
"ActorConnectOptions",
"ActorRef",
"ActorRegistry",
"ActorsModule",
"AgentName",
"AgentNameRegistry",
"AgentsModule",
Expand All @@ -9,12 +13,14 @@
"AppModule",
"AppPublicSettingsResponse",
"AuthModule",
"Connection",
"ConnectorApiRequest",
"ConnectorApiResponse",
"ConnectorApiResponsePhase",
"ConnectorIntegrationType",
"ConnectorIntegrationTypeRegistry",
"ConnectorsModule",
"CoreIntegrations",
"CustomIntegrationsModule",
"DeleteManyResult",
"DeleteResult",
Expand All @@ -27,7 +33,6 @@
"FunctionsModule",
"ImportResult",
"IntegrationsModule",
"CoreIntegrations",
"SortField",
"SsoModule",
"UpdateManyResult"
Expand Down
184 changes: 156 additions & 28 deletions src/modules/actors.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
* Extend this interface to add typed `subscribe` callbacks and `send` payloads
* for your deployed Actors.
*
* This is separate from {@link ActorNameRegistry} (which is auto-generated
* This is separate from `ActorNameRegistry` (which is auto-generated
* by `base44 types generate`), so there are no conflicts.
*
* @example
* ```typescript
* // Declare message types for a deployed actor
* declare module "@base44/sdk" {
* interface ActorRegistry {
* ChatRoom: {
Expand All @@ -21,7 +22,7 @@ export interface ActorRegistry {}

/**
* Auto-populated by `base44 types generate` with the names of your deployed actors.
* Do not edit this interface manually. Use {@link ActorRegistry} for message types.
* Do not edit this interface manually. Use `ActorRegistry` for message types.
*/
export interface ActorNameRegistry {}

Expand All @@ -39,83 +40,210 @@ type ToServerFor<N extends string> = N extends keyof ActorRegistry
: unknown
: unknown;

/** Options for {@link ActorRef.connect}. */
/** Options for [connect()](#connect). */
export interface ActorConnectOptions {
/**
* The connection id, used as the actor's `conn.id`. Supply a stable value
* (e.g. persisted per tab) so a reconnect reuses the same server-side
* identity; omit for an auto-generated per-connection id.
* The connection id, used as the actor's `conn.id`. Supply a stable value,
* such as one persisted per tab, so a reconnect reuses the same server-side
* identity. Omit it for an auto-generated per-connection id.
*/
id?: string;
}

/** Handle for one listener registered via {@link Connection.subscribe}. */
/** Handle for one listener registered via `subscribe()`. */
export interface ActorSubscription {
/** Remove this listener; other listeners and the socket stay live. */
/**
* Removes this listener. Other listeners on the same connection, and the
* connection itself, stay live. To close the connection as well, call
* [close()](#close).
*
* @example
* ```typescript
* // Stop listening without closing the connection
* const sub = conn.subscribe((msg) => console.log(msg));
* sub.unsubscribe();
* ```
*/
unsubscribe(): void;
}

/**
* A live connection to an actor instance, returned by {@link ActorRef.connect}.
* A live connection to an actor instance, returned by [connect()](#connect).
* `subscribe`/`send` are always valid. You only get a `Connection` once the
* socket has been opened, so there's no pre-connect state to guard against.
*/
export interface Connection<N extends string = string> {
/** The connection id (the value the actor sees as `conn.id`). */
/** The connection id, which is the value the actor sees as `conn.id`. */
readonly id: string;

/** Register a message listener. Multiple are allowed; returns a per-listener unsubscribe. */
/**
* Registers a listener for messages sent by the actor. Any number of
* listeners can be registered on one connection, and each receives every
* message.
*
* The callback payload is typed when the actor is declared in
* [ActorRegistry](#actorregistry), and is `unknown` otherwise.
*
* @param callback - Called with each message the actor sends to this client.
* @returns An `ActorSubscription` that removes this one listener.
*
* @example
* ```typescript
* // Listen for messages from the actor
* const sub = conn.subscribe((msg) => {
* if (msg.type === "message") console.log(msg.text);
* });
* ```
*/
subscribe(callback: (data: ToClientFor<N>) => void): ActorSubscription;

/** Send a message. Buffered by the socket until it's open; dropped after
* {@link close}. */
/**
* Sends a message to the actor.
*
* Messages sent before the socket finishes opening are buffered and flushed
* on open. Messages sent after {@link close} are dropped silently.
*
* The payload is typed when the actor is declared in
* [ActorRegistry](#actorregistry), and is `unknown` otherwise.
*
* @param data - The message to send to the actor.
*
* @example
* ```typescript
* // Send a message to the actor
* conn.send({ type: "message", text: "hi" });
* ```
*/
send(data: ToServerFor<N>): void;

/**
* Tear down the socket, heartbeat, and all listeners. Safe to call more
* than once. A connection also closes itself when it fails permanently.
* See {@link ActorRef.connect}.
* Closes the connection, tearing down the socket, the heartbeat, and every
* listener registered on it. Safe to call more than once.
*
* A connection also closes itself when it fails permanently. See
* [connect()](#connect) for how to recover from that.
*
* @example
* ```typescript
* // Close when the view that opened the connection goes away.
* useEffect(() => {
* const conn = base44.actors.ChatRoom(roomId).connect();
* conn.subscribe(setMessage);
* return () => conn.close();
* }, [roomId]);
* ```
*/
close(): void;
}

/**
* A handle to one actor instance, obtained from `base44.actors.MyActor(id)`. Call
* {@link connect} to open the socket and get a {@link Connection}.
* {@link connect} to open the socket and get a [Connection](#connection).
*/
export interface ActorRef<N extends string = string> {
/**
* Open the WebSocket and return the {@link Connection}. Idempotent while the
* connection is open.
* Opens the WebSocket to this actor instance and returns the
* [Connection](#connection). Idempotent while the connection is open, so calling it
* again returns the same connection rather than opening a second socket.
*
* The returned connection is usable straight away. Messages passed to
* [send()](#send) before the socket finishes opening are buffered and
* flushed on open.
*
* A connection that fails permanently, for example because the actor doesn't
* exist or the caller isn't allowed to connect, closes itself and reports the
* error to the client's `onError` handler. Call `connect()` again once the
* cause is fixed to get a fresh [Connection](#connection), then re-subscribe, as
* listeners do not carry over.
*
* @param options - Connection options. See [ActorConnectOptions](#actorconnectoptions).
* @returns A live [Connection](#connection) to this actor instance.
*
* @example
* ```typescript
* // Connect to an actor instance
* const conn = base44.actors.ChatRoom("room-1").connect();
* ```
*
* @example
* ```typescript
* // Reuse a stable connection id so a reconnect keeps the same
* // server-side identity.
* let id = sessionStorage.getItem("chat-conn-id") ?? crypto.randomUUID();
* sessionStorage.setItem("chat-conn-id", id);
*
* A connection that fails permanently (for example, the actor doesn't exist
* or the caller isn't allowed to connect) closes itself and reports the
* error to the client's `onError` handler. Call `connect()` again after
* fixing the cause to get a fresh {@link Connection}, and re-subscribe.
* const conn = base44.actors.ChatRoom("room-1").connect({ id });
* ```
*/
connect(options?: ActorConnectOptions): Connection<N>;
}

/**
* Client for a single named Actor. Call it with an instance id to get an
* {@link ActorRef}. Typed automatically when the actor is registered in
* {@link ActorRegistry}.
* [ActorRef](#actorref). Typed automatically when the actor is registered in
* [ActorRegistry](#actorregistry).
*/
export interface ActorClient<N extends string = string> {
(instanceId: string): ActorRef<N>;
}

/**
* The actors module provides access to Cloudflare Durable Object-backed
* Actors module for real-time messaging with Cloudflare Durable Object-backed
* Actors deployed by the Base44 platform.
*
* An Actor is a named server-side object with persistent state. Each instance
* is addressed by an id, so `base44.actors.ChatRoom("room-1")` and
* `base44.actors.ChatRoom("room-2")` are separate instances with separate
* state. Clients open a WebSocket to an instance and exchange messages with it.
*
* This module provides:
* - Per-instance WebSocket connections, opened with [connect()](#connect)
* - Message listeners, registered with [subscribe()](#subscribe)
* - Message sending, with [send()](#send)
* - Automatic reconnection with backoff, including recovery from half-open
* sockets that stop delivering messages without emitting a close event
* - End-to-end typing of message payloads through [ActorRegistry](#actorregistry)
*
* This module is available to use with a client in anonymous and user
* authentication modes. It is not available on `base44.asServiceRole`.
*
* Connections stay open until you call [close()](#close), so close them
* when the view that opened them goes away.
*
* @example
* ```typescript
* const conn = base44.actors.MyActor("room-1").connect();
* const sub = conn.subscribe((msg) => console.log(msg)); // typed via ActorRegistry
* // Open a connection to one instance of the ChatRoom actor.
* const conn = base44.actors.ChatRoom("room-1").connect();
*
* const sub = conn.subscribe((msg) => {
* console.log(msg);
* });
*
* conn.send({ type: "message", text: "hi" });
*
* // Later, when the view goes away.
* sub.unsubscribe();
* conn.close();
* ```
*
* @example
* ```typescript
* // Register the actor to type both directions of the conversation.
* declare module "@base44/sdk" {
* interface ActorRegistry {
* ChatRoom: {
* toClient: { type: "joined" | "message"; from?: string; text?: string };
* toServer: { type: "message"; text: string };
* };
* }
* }
*
* const conn = base44.actors.ChatRoom("room-1").connect();
* conn.subscribe((msg) => {
* // msg is typed as the toClient union.
* if (msg.type === "message") console.log(msg.text);
* });
* ```
*/
export type ActorsModule = {
[K in AllActorNames]: K extends keyof ActorRegistry
Expand Down
Loading