Skip to content

Readonly connections

Last updated View as MarkdownAgent setup

A readonly connection receives state updates but cannot change state. The Agent class uses the readonly support in the WebSockets capability and adds methods for it.

import { Agent, callable, getCurrentAgent } from "agents";

export class DocAgent extends Agent {
	initialState = { text: "" };

	shouldConnectionBeReadonly(_connection, ctx) {
		return new URL(ctx.request.url).searchParams.get("mode") === "view";
	}

	@callable()
	async edit(text) {
		// Throws "Connection is readonly" for a readonly caller.
		this.setState({ text });
	}

	@callable()
	async promote() {
		const { connection } = getCurrentAgent();
		if (connection) this.setConnectionReadonly(connection, false);
	}
}
import {
	Agent,
	callable,
	getCurrentAgent,
	type Connection,
	type ConnectionContext,
} from "agents";

type Doc = { text: string };

export class DocAgent extends Agent<Env, Doc> {
	initialState: Doc = { text: "" };

	shouldConnectionBeReadonly(_connection: Connection, ctx: ConnectionContext) {
		return new URL(ctx.request.url).searchParams.get("mode") === "view";
	}

	@callable()
	async edit(text: string) {
		// Throws "Connection is readonly" for a readonly caller.
		this.setState({ text });
	}

	@callable()
	async promote() {
		const { connection } = getCurrentAgent();
		if (connection) this.setConnectionReadonly(connection, false);
	}
}
Agent method Description
shouldConnectionBeReadonly(connection, ctx) Decides when a connection is accepted. Defaults to false
setConnectionReadonly(connection, readonly?) Changes it later. Pass false to make the connection writable
isConnectionReadonly(connection) Returns whether the connection is readonly

On the client, a refused write calls onStateUpdateError in useAgent or AgentClient.

What readonly blocks

Readonly is enforced inside setState(). It blocks client-side setState() calls and any @callable() method that calls this.setState(). It does not block anything else. A callable that does work before it writes state still does that work for a readonly caller, so write state first, or check isConnectionReadonly() before side effects.

Do not trust a query parameter on its own to decide who can write. Check the user in your Worker before you route the request. For more information, refer to Authentication.

Was this helpful?