@firtoz/socka
Version:
Standard Schema–first WebSocket RPC for TypeScript — Bun, Hono, Node ws, Cloudflare Workers, Durable Objects
97 lines (75 loc) • 4.33 kB
Markdown
# React + Cloudflare Durable Objects
This page is a **minimal wiring guide** for the common stack: **one shared `defineSocka` contract**, a **Durable Object** extending **`SockaWebSocketDO`**, and a **React** client using **`useSockaSession`**. A full app with routing and persistence is **[chatroom-do](../../examples/chatroom-do)** in this repo.
## Install
You need **`@firtoz/socka`**, **`@firtoz/socka/do`**, **`@firtoz/socka/react`**, and peer deps for Cloudflare and **`@firtoz/websocket-do`**—see **[Peers](./peers.md)** and **[Durable Objects](./durable-objects.md)** (Wrangler, `Env` types, **do not** hand-edit generated worker env `.d.ts` files).
## Shared contract (no client casts)
Export **one** contract from a shared module and import the **same reference** in the worker and the browser. You should **not** need `as never`, `as unknown`, manual `send` types, or `InferSockaSend` in normal app code—if TypeScript widens or fails to infer, align **Zod (or other Standard Schema) output** with any hand-written types (see **[TypeScript and exact optional properties](./reference.md#typescript-and-exact-optional-properties)**) and re-export **types** from the schema with **`z.infer`** when possible.
```ts
// shared/contract.ts
import { defineSocka } from "@firtoz/socka/core";
import { z } from "zod";
export const roomContract = defineSocka({
calls: {
list: {
input: z.object({}).optional(),
output: z.object({ items: z.array(z.string()) }),
},
sendCursor: {
input: z.object({ x: z.number(), y: z.number() }),
// no `output` — fire-and-forget; see README “Call `output` shapes”
},
},
pushes: {
cursorBatch: z.object({
cursors: z.array(z.object({ x: z.number(), y: z.number() })),
}),
},
});
```
**Output shapes** — For high-frequency messages (cursors, live drafts), **omit** `output`. Use **`output: z.void()`** when the client should **await** a server ack. See **[Client — Fire-and-forget](./client.md#fire-and-forget)** and **[Reference — Optional output](./reference.md#optional-output-fire-and-forget)**.
## Durable Object
Subclass **`SockaWebSocketDO`**. Declare **`protected readonly contract`** and implement **`buildSockaSessionConfig`**. Full patterns: **[Durable Objects](./durable-objects.md)**, example **[`examples/chatroom-do/src/do.ts`](../../examples/chatroom-do/src/do.ts)**.
```ts
// do.ts (sketch)
import {
SockaWebSocketDO,
type SockaDoSessionConfigInput,
} from "@firtoz/socka/do";
import { roomContract } from "./contract";
type SessionData = { userId: string };
export class RoomDo extends SockaWebSocketDO<
typeof roomContract,
SessionData,
Env
> {
protected readonly contract = roomContract;
protected buildSockaSessionConfig(
ctx: Context<{ Bindings: Env }> | undefined,
): SockaDoSessionConfigInput<typeof roomContract, SessionData, Env> {
return { handlers: { … }, handleClose: async () => {} };
}
}
```
## React client
Use **`useSockaSession(contract, { url, pushHandlers? }, deps)`** with the **same** `roomContract` import. For **`pushHandlers`**, you can use **`satisfies Partial<InferSockaPushHandlers<typeof roomContract>>`**—see **[Pushes — Typing `pushHandlers`](./pushes.md#typing-pushhandlers)**.
```tsx
// RoomClient.tsx (sketch; URL must exist before connecting — see Client “SSR and WebSocket URLs”)
import { useSockaSession } from "@firtoz/socka/react";
import { roomContract } from "./contract";
function RoomClient({ url }: { url: string }) {
const { ready, send } = useSockaSession(
roomContract,
{ url, pushHandlers: { cursorBatch: (p) => { /* set state */ } } },
[url],
);
// `await send.list({})` — request/response
// `void send.sendCursor({ x, y })` — fire-and-forget; see observability in Client + Reference
return null;
}
```
Whiteboard-style contracts (ops + draft + cursors) are sketched in **[Collaborative realtime](./collaborative-realtime.md)**.
## See also
- **[Client](./client.md)** — `useSockaSession`, **SSR and WebSocket URLs**, fire-and-forget **observability**
- **[Reference](./reference.md)** — `InferSocka*` types, `reportError`, `SockaReportError` kinds
- **[Durable Objects](./durable-objects.md)** — hibernation, `session.update()`, `createData`
- **[chatroom-do](../../examples/chatroom-do)** — SQLite + Drizzle + UI