Runtime API
ayjnt's runtime is deliberately thin. Most of the framework is in build-time codegen; the runtime is a handful of re-exports and one compose helper.
Import paths at a glance
| Import path | What it gives you | Source |
|---|---|---|
ayjnt | Agent, getAgent, VERSION | ayjnt package |
ayjnt/rpc | getAgent<T> | ayjnt package |
ayjnt/middleware | Middleware, Context, Next (types); compose, createContext (used by
generated code)
| ayjnt package |
@ayjnt/env | GeneratedEnv | Generated at .ayjnt/env.d.ts |
@ayjnt/<route> | useAgent typed to that agent
| Generated at .ayjnt/client/<route>/index.tsx |
ayjnt
Agent
Re-exported from agents. The base class for every
Durable Object agent.
import { Agent } from "ayjnt";
// Equivalent to:
import { Agent } from "agents";
class MyAgent extends Agent<Env, State> {
override initialState: State = { /* ... */ };
override async onRequest(request: Request): Promise<Response> { /* ... */ }
}
Generics: Agent<Env, State, Props>. Env is the worker env (DO bindings + KV/R2/vars). State is the shape of this.state. Props is optional per-instance metadata (used less
often). Exhaustive reference in the
Cloudflare Agents docs
.
getAgent
import { getAgent } from "ayjnt";
// or:
import { getAgent } from "ayjnt/rpc";
Typed inter-agent RPC stub. See ayjnt/rpc below.
VERSION
import { VERSION } from "ayjnt";
console.log(VERSION); // "0.1.0" at time of writingayjnt/rpc
getAgent<T>(namespace, name)
async function getAgent<T extends Rpc.DurableObjectBranded | undefined>(
namespace: DurableObjectNamespace<T>,
name: string,
): Promise<DurableObjectStub<T>>;
Thin wrapper over the SDK's getAgentByName with
the generic parameter order reshuffled so the call site reads as getAgent<ChatAgent>(env.CHAT_AGENT, id)
instead of threading Env through. Under the hood:
namespace.idFromName(name)→ DO idnamespace.get(id)→ stubstub.setName(name)→ DO learns its identity- returns the stub
The returned stub exposes every public method on T
with full type inference, plus .fetch(request) for
HTTP-over-DO.
For the full pattern — including exception propagation and structured-clone argument limits — see Inter-agent RPC .
ayjnt/middleware
Middleware (type)
type Middleware<Env = unknown> = (
c: Context<Env>,
next: () => Promise<Response>,
) => Promise<Response> | Response;
The signature every middleware.ts default-exports.
Return a Response to short-circuit; call next() to
continue; wrap next()'s return value to modify
the inner response.
Context (type)
type Context<Env = unknown> = {
readonly request: Request;
readonly url: URL;
readonly env: Env;
readonly executionCtx: ExecutionContext;
readonly params: {
instanceId: string;
pathSuffix: string;
};
json(body: unknown, init?: number | ResponseInit): Response;
text(body: string, init?: number | ResponseInit): Response;
html(body: string, init?: number | ResponseInit): Response;
redirect(location: string, status?: number): Response;
set(key: string, value: unknown): void;
get<T = unknown>(key: string): T | undefined;
};Full description in Middleware .
Next (type)
type Next = () => Promise<Response>;compose + createContext
These are exported from the module but only meant to be called by
the generated entry.ts. You can use them if you're
writing your own dispatch, but there's rarely a reason to.
function compose<Env>(
stack: Middleware<Env>[],
ctx: Context<Env>,
finalize: () => Promise<Response>,
): Promise<Response>;
function createContext<Env>(init: {
request: Request;
url: URL;
env: Env;
executionCtx: ExecutionContext;
params: Context<Env>["params"];
}): Context<Env>;@ayjnt/env
import type { GeneratedEnv } from "@ayjnt/env";
Regenerated on every ayjnt build. Declares every DO
binding for every agent in your tree, each typed against the
specific agent class. Shape:
// .ayjnt/env.d.ts (generated)
import type ChatAgent from "../agents/chat/agent.ts";
import type OrdersAgent from "../agents/orders/agent.ts";
export type GeneratedEnv = {
CHAT_AGENT: DurableObjectNamespace<ChatAgent>;
ORDERS_AGENT: DurableObjectNamespace<OrdersAgent>;
};
To use: extend it with your own non-DO bindings and declare the
result as the Agent's first generic:
type Env = GeneratedEnv & {
KV: KVNamespace;
JWT_SECRET: string;
};
export default class MyAgent extends Agent<Env, State> {
// this.env.CHAT_AGENT is typed to DurableObjectNamespace<ChatAgent>
// this.env.KV, this.env.JWT_SECRET work too
}@ayjnt/env resolves via tsconfig paths. See
Co-located UI
for the exact config.
@ayjnt/<route>
One path per agent — @ayjnt/chat, @ayjnt/admin/users, etc. Each exports a typed useAgent hook bound to that agent's class and
route prefix.
import { useAgent } from "@ayjnt/counter";
function Counter() {
const agent = useAgent();
// agent.state is typed to CounterAgent's State
// agent.setState round-trips through the DO
// agent.name is the DO instance name (from the URL)
return <div>{agent.state?.count ?? 0}</div>;
}Options
useAgent<State = InferredFromAgentClass>(options?: {
// All standard agents/react UseAgentOptions except agent and basePath
name?: string; // Override URL-derived instance name
onOpen?: () => void;
onClose?: () => void;
onError?: (e: Event) => void;
onMessage?: (m: MessageEvent) => void;
onStateUpdate?: (state: State, source: "server" | "client") => void;
query?: Record<string, string>;
}) agent and basePath are set automatically;
override name to pick an instance that doesn't
come from the URL. Everything else passes through to the upstream useAgent.
Return value
An AgentClient instance from the SDK, with .state, .setState, .name, .agent, WebSocket event handlers, method proxies, and
an async ready promise that resolves once the first CF_AGENT_IDENTITY message arrives. Full shape in the
Cloudflare client SDK reference
.