@firtoz/socka 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +191 -40
  2. package/dist/{SockaWebSocketSession-Bru8yFcK.d.ts → SockaWebSocketSession-Cza7Fti-.d.ts} +87 -5
  3. package/dist/bun/index.d.ts +28 -3
  4. package/dist/bun/index.js +28 -5
  5. package/dist/bun/index.js.map +1 -1
  6. package/dist/{chunk-MZCQHJXY.js → chunk-2FNWVCP3.js} +27 -8
  7. package/dist/chunk-2FNWVCP3.js.map +1 -0
  8. package/dist/{chunk-AM7PB26G.js → chunk-H3S3435J.js} +125 -3
  9. package/dist/chunk-H3S3435J.js.map +1 -0
  10. package/dist/{chunk-45D4T232.js → chunk-JVLUA3Q5.js} +64 -6
  11. package/dist/chunk-JVLUA3Q5.js.map +1 -0
  12. package/dist/chunk-KQO5AVKA.js +8 -0
  13. package/dist/chunk-KQO5AVKA.js.map +1 -0
  14. package/dist/client/index.d.ts +59 -3
  15. package/dist/client/index.js +2 -2
  16. package/dist/core/index.d.ts +5 -1
  17. package/dist/core/index.js +1 -1
  18. package/dist/do/index.d.ts +20 -2
  19. package/dist/do/index.js +35 -2
  20. package/dist/do/index.js.map +1 -1
  21. package/dist/hono/cloudflare-workers.d.ts +2 -2
  22. package/dist/hono/cloudflare-workers.js +4 -3
  23. package/dist/hono/cloudflare-workers.js.map +1 -1
  24. package/dist/hono/index.d.ts +20 -4
  25. package/dist/hono/index.js +5 -3
  26. package/dist/hono/index.js.map +1 -1
  27. package/dist/react/index.d.ts +43 -4
  28. package/dist/react/index.js +103 -9
  29. package/dist/react/index.js.map +1 -1
  30. package/dist/server/index.d.ts +17 -4
  31. package/dist/server/index.js +24 -4
  32. package/dist/server/index.js.map +1 -1
  33. package/dist/{socka-report-error-DzFI2Tr7.d.ts → socka-report-error-ixTynx4w.d.ts} +8 -1
  34. package/dist/test/index.d.ts +11 -0
  35. package/dist/test/index.js +84 -0
  36. package/dist/test/index.js.map +1 -0
  37. package/docs/README.md +15 -6
  38. package/docs/auth.md +27 -0
  39. package/docs/backpressure.md +16 -0
  40. package/docs/client.md +44 -3
  41. package/docs/comparison.md +1 -1
  42. package/docs/durable-objects.md +2 -2
  43. package/docs/getting-started.md +143 -84
  44. package/docs/history.md +26 -0
  45. package/docs/internals.md +56 -0
  46. package/docs/lifecycle.md +3 -3
  47. package/docs/multi-room.md +10 -8
  48. package/docs/peers.md +11 -7
  49. package/docs/presence.md +43 -0
  50. package/docs/{events.md → pushes.md} +1 -1
  51. package/docs/recipes.md +78 -0
  52. package/docs/reconnection.md +44 -0
  53. package/docs/reference.md +21 -30
  54. package/docs/server.md +14 -1
  55. package/docs/testing.md +20 -0
  56. package/docs/wire-format.md +25 -0
  57. package/examples/minimal-socka.ts +56 -3
  58. package/package.json +14 -10
  59. package/dist/chunk-45D4T232.js.map +0 -1
  60. package/dist/chunk-AM7PB26G.js.map +0 -1
  61. package/dist/chunk-MZCQHJXY.js.map +0 -1
package/docs/client.md CHANGED
@@ -28,8 +28,14 @@ const rows = await session.send.list();
28
28
 
29
29
  Use **`SockaWebSocketClient`** directly if you need **`onResponse` / `onServerError` / `onEvent`** frame hooks without **`SockaSession`**’s typed **`send`** / **`subscribe`**; most apps use **`SockaSession`**.
30
30
 
31
+ **Connection status** — **`SockaWebSocketClient`** and **`SockaSession`** expose **`status`** (`"idle" | "connecting" | "open" | "reconnecting" | "closed"`) and **`onStatusChange`** for UI (e.g. “Reconnecting…”). Same fields power the React hooks below.
32
+
31
33
  ## React
32
34
 
35
+ ### `useSockaSession` — typed `send`
36
+
37
+ Use **`useSockaSession(contract, options, deps)`** when you want **`send.*`** RPC methods directly (same shape as **`session.send`**). **`ready`** flips to **`true`** after the socket opens; **`deps`** remount the connection when identity (e.g. room id) changes. Also returns **`status`**, **`reconnecting`**, and **`reconnectAttempt`** (see **`useSocka`**).
38
+
33
39
  ```ts
34
40
  import { useSockaSession } from "@firtoz/socka/react";
35
41
  import { myContract } from "./contract";
@@ -44,6 +50,41 @@ function App() {
44
50
  useSockaSession(myContract, { url: "wss://...", wireFormat: "msgpack" }, []);
45
51
  ```
46
52
 
53
+ ### `useSocka` — hold a `SockaSession` ref
54
+
55
+ Use **`useSocka(options, deps)`** when you need the full **`SockaSession`** (e.g. **`session.subscribe`**, **`session.client`**, **`waitForPush`**, or passing the session into non-React helpers). It returns **`{ ready, sessionRef, status, reconnecting, reconnectAttempt }`** — read **`sessionRef.current`** in effects or callbacks (it is **`null`** until the effect runs). Use **`reconnecting`** or **`status === "reconnecting"`** for banners; **`reconnectAttempt`** counts backoff attempts.
56
+
57
+ | | **`useSockaSession`** | **`useSocka`** |
58
+ |--|------------------------|----------------|
59
+ | **Returns** | **`{ ready, send, status, reconnecting, reconnectAttempt }`** | **`{ ready, sessionRef, status, reconnecting, reconnectAttempt }`** |
60
+ | **Best for** | Most React UIs that only call RPCs | Subscriptions, low-level client access, imperative APIs |
61
+
62
+ ```tsx
63
+ import { useEffect } from "react";
64
+ import { useSocka } from "@firtoz/socka/react";
65
+ import { myContract } from "./contract";
66
+
67
+ function App() {
68
+ const { ready, sessionRef } = useSocka({ contract: myContract, url: "ws://..." }, []);
69
+
70
+ useEffect(() => {
71
+ const s = sessionRef.current;
72
+ if (!ready || !s) return;
73
+ const onNotify = (p: { msg: string }) => console.log(p.msg);
74
+ s.subscribe.on("notify", onNotify);
75
+ return () => s.subscribe.off("notify", onNotify);
76
+ }, [ready, sessionRef]);
77
+
78
+ return null;
79
+ }
80
+ ```
81
+
82
+ *(Example assumes your contract defines a **`notify`** push; use the real push names from **`myContract`**.)*
83
+
84
+ ### `useSockaPresence` — snapshot + join/leave deltas
85
+
86
+ Call **`useSockaPresence(sessionRef, ready, options, deps)`** after **`useSocka`** / **`useSockaSession`**: it runs your **`snapshot`** RPC once, subscribes to **`joinPush`** / **`leavePush`**, and returns **`{ users, selfUserId, loading }`**. Pass the same **`deps`** as the connection when room identity changes.
87
+
47
88
  ### One WebSocket for the whole tree
48
89
 
49
90
  If many components need **`send`**, avoid calling **`useSockaSession`** in each one (each call owns a connection). Mount a provider once and read the session from context:
@@ -65,7 +106,7 @@ function Layout({ roomId }: { roomId: string }) {
65
106
  }
66
107
 
67
108
  function Child() {
68
- const { ready, send } = useSockaSessionContext(myContract);
109
+ const { ready, send, status, reconnecting } = useSockaSessionContext(myContract);
69
110
  // ...
70
111
  }
71
112
  ```
@@ -78,8 +119,8 @@ Use **`autoConnect: false`** on **`SockaWebSocketClient`** / **`SockaSession`**
78
119
 
79
120
  ## Client lifecycle
80
121
 
81
- Treat each **`SockaSession`** / **`SockaWebSocketClient`** as bound to **one** underlying **`WebSocket`**. When the socket closes, pending calls reject and should not be retried on the same instance. For reconnect or room changes, construct a **new** client (in React, remount **`useSockaSession`** / **`SockaSessionProvider`** when **`url`** or identity **`deps`** change). Use **`ready`** / **`waitForOpen()`** before assuming the connection is usable; use **`onClose`** / **`onError`** (or **`reportError`**) for backoff, toasts, or logging.
122
+ Treat each **`SockaSession`** / **`SockaWebSocketClient`** as bound to **one** underlying **`WebSocket`**. When the socket closes, pending calls reject on that instance unless you opt into client-side reconnect (see **[Reconnection](./reconnection.md)**). For a deliberate room/url change, construct a **new** client (in React, remount **`useSockaSession`** / **`SockaSessionProvider`** when **`url`** or identity **`deps`** change). Use **`ready`** / **`waitForOpen()`** before assuming the connection is usable; use **`onClose`** / **`onError`** (or **`reportError`**) for telemetry.
82
123
 
83
124
  ## Pushes
84
125
 
85
- Server push and client subscriptions are covered in [Pushes](./events.md).
126
+ Server push and client subscriptions are covered in **[Pushes](./pushes.md)**.
@@ -33,4 +33,4 @@ Most apps model messages as large discriminated unions (`type` + `id`), validate
33
33
 
34
34
  ## When socka is a good fit
35
35
 
36
- You want **schema-first WebSocket RPC** with **correlated request/response** and optional **typed server push** from a **single contract module**—and you are fine with **socka v1** frames (see **[Reference](./reference.md)**) so you can swap runtimes (Bun, Hono, Durable Objects, Node **`ws`**) behind the same procedures.
36
+ You want **schema-first WebSocket RPC** with **correlated request/response** and optional **typed server push** from a **single contract module**—and you are fine with **socka v1** frames (see **[Internals](./internals.md)**) so you can swap runtimes (Bun, Hono, Durable Objects, Node **`ws`**) behind the same procedures.
@@ -14,11 +14,11 @@ This is the **Cloudflare** side (bindings, Wrangler, generated types)—not sock
14
14
  1. **Wrangler** — `wrangler.jsonc` / `wrangler.toml` with a **Durable Object** binding and a **migration** for the DO class (see Cloudflare docs and the runnable **[tic-tac-toe-do example](https://github.com/firtoz/fullstack-toolkit/tree/main/examples/tic-tac-toe-do)** in this repo).
15
15
  2. **Typed `Env`** — After you change bindings or `wrangler` config, regenerate env types (**`bun run typegen`** / **`cf-typegen`** via [`@firtoz/worker-helper`](https://github.com/firtoz/fullstack-toolkit/tree/main/packages/worker-helper)); **do not hand-edit** `worker-configuration.d.ts`. Workflow reference: [Cloudflare / Wrangler typegen skill](https://github.com/firtoz/fullstack-toolkit/blob/main/.cursor/skills/cloudflare-wrangler-typegen/SKILL.md) in this monorepo.
16
16
  3. **Run locally** — `wrangler dev` (optionally pin a port—e.g. **3463** in the tic-tac-toe example so it does not clash with other apps).
17
- 4. **Peers** — **`@firtoz/socka/do`** needs **`@firtoz/websocket-do`**, **`hono`**, **`@cloudflare/workers-types`**—see **[Peers](./peers.md)**.
17
+ 4. **Peers** — **`@firtoz/socka/do`** needs **`@firtoz/websocket-do`** and **`hono`** see **[Peers](./peers.md)**. Use **`wrangler types`** (or your app’s typegen) for Cloudflare/DO bindings in TypeScript.
18
18
 
19
19
  ### Wire format
20
20
 
21
- **`wireFormat`** defaults to **`"json"`** (text frames). Use **`"msgpack"`** only if the client also uses **`msgpack`**. Mismatched **`wireFormat`** between client and session config will fail to decode. Details: **[Reference — Wire encoding](./reference.md#wire-encoding-json-and-msgpack)**.
21
+ **`wireFormat`** defaults to **`"json"`** (text frames). Use **`"msgpack"`** only if the client also uses **`msgpack`**. Mismatched **`wireFormat`** between client and session config will fail to decode. See **[Reference](./reference.md#wire-encoding-json-and-msgpack)** and **[Internals](./internals.md)**.
22
22
 
23
23
  ## `SockaDoSession`
24
24
 
@@ -1,10 +1,25 @@
1
1
  # Getting started
2
2
 
3
- The **[README](../README.md)** opens with a **complete Bun example**: shared contract, **`createSockaBunWebSocketHandlers`**, and **`SockaSession`**. Start there if you want something runnable in one minute.
3
+ This guide walks through a **multi-room chat** on one **`defineSocka`** contract: **typed RPCs** (`listHistory`, `listPresence`, `sendMessage`, `clearHistory`) and **typed pushes** (`userJoined`, `userLeft`, `roomMessage`, `historyCleared`). The **[README](../README.md)** has the shortest runnable **Bun** slice (in-memory history).
4
4
 
5
- ## Quickest path (Bun)
5
+ **Runnable apps** with persistence and a **multi-room browser client**: **[chatroom-bun](../../../examples/chatroom-bun)** (Bun SQLite), **[chatroom-hono](../../../examples/chatroom-hono)** (JSON files), **[chatroom-do](../../../examples/chatroom-do)** (Durable Object SQLite + Drizzle).
6
6
 
7
- Save three files next to each other, then run **`bun run server.ts`**. Point a client at **`ws://localhost:3450/ws`** (same contract as the README).
7
+ For API tables, see **[Reference](./reference.md)**. For wire details, **[Internals](./internals.md)**.
8
+
9
+ ---
10
+
11
+ ## Step 1 — What you are building
12
+
13
+ 1. Clients connect to **`ws://host/ws/<roomId>?name=<displayName>`** (path and query are conventions you control).
14
+ 2. Each **room** has its own **`sessionMap`** and **config** — see **[Multi-room](./multi-room.md)**.
15
+ 3. **Join/leave** are **`pushes`** to everyone else in the room; **chat lines** are **`pushes`** too (after you persist).
16
+ 4. **History** is loaded with a **call** (`listHistory`) so reconnects and new panes can hydrate from storage (SQLite, JSON files, or DO SQLite in the examples).
17
+
18
+ ---
19
+
20
+ ## Step 2 — Shared contract
21
+
22
+ Use one module on the client and every server:
8
23
 
9
24
  **`contract.ts`**
10
25
 
@@ -12,127 +27,171 @@ Save three files next to each other, then run **`bun run server.ts`**. Point a c
12
27
  import { defineSocka } from "@firtoz/socka/core";
13
28
  import * as z from "zod";
14
29
 
15
- export const myContract = defineSocka({
16
- calls: {
17
- echo: {
18
- input: z.object({ text: z.string() }),
19
- output: z.object({ text: z.string() }),
20
- },
21
- },
30
+ export const messageRow = z.object({
31
+ id: z.string(),
32
+ ts: z.number(),
33
+ userId: z.string(),
34
+ displayName: z.string(),
35
+ text: z.string(),
22
36
  });
23
- ```
24
-
25
- **`server.ts`**
26
37
 
27
- ```ts
28
- import { createSockaBunWebSocketHandlers } from "@firtoz/socka/bun";
29
- import { myContract } from "./contract";
38
+ export type ChatMessageRow = z.infer<typeof messageRow>;
30
39
 
31
- const { websocket } = createSockaBunWebSocketHandlers({
32
- contract: myContract,
33
- handlers: {
34
- echo: async (input) => ({ text: input.text }),
35
- },
36
- handleClose: async () => {},
40
+ const onlineUser = z.object({
41
+ userId: z.string(),
42
+ displayName: z.string(),
37
43
  });
38
44
 
39
- Bun.serve({
40
- port: 3450,
41
- fetch(req, server) {
42
- if (new URL(req.url).pathname === "/ws") {
43
- if (server.upgrade(req)) return undefined;
44
- return new Response("WebSocket upgrade failed", { status: 400 });
45
- }
46
- return new Response("OK");
45
+ export const chatContract = defineSocka({
46
+ calls: {
47
+ listHistory: {
48
+ input: z.object({ limit: z.number().int().min(1).max(500).optional() }),
49
+ output: z.object({ messages: z.array(messageRow) }),
50
+ },
51
+ listPresence: {
52
+ input: z.object({}).optional(),
53
+ output: z.object({
54
+ selfUserId: z.string(),
55
+ users: z.array(onlineUser),
56
+ }),
57
+ },
58
+ sendMessage: {
59
+ input: z.object({ text: z.string().min(1) }),
60
+ output: z.object({ ok: z.literal(true) }),
61
+ },
62
+ clearHistory: {
63
+ input: z.object({}).optional(),
64
+ output: z.object({ ok: z.literal(true) }),
65
+ },
66
+ },
67
+ pushes: {
68
+ userJoined: z.object({ userId: z.string(), displayName: z.string() }),
69
+ userLeft: z.object({
70
+ userId: z.string(),
71
+ displayName: z.string(),
72
+ }),
73
+ roomMessage: messageRow,
74
+ historyCleared: z.object({
75
+ ts: z.number(),
76
+ clearedByUserId: z.string(),
77
+ clearedByDisplayName: z.string(),
78
+ }),
47
79
  },
48
- websocket,
49
80
  });
50
81
  ```
51
82
 
52
- **`client.ts`**
83
+ ---
84
+
85
+ ## Step 3 — Client: subscribe, hydrate, send
86
+
87
+ 1. Open **`SockaSession`** with **`url`** pointing at your upgrade path (same **`roomId`** and **`name`** the server parses in **`createData`**).
88
+ 2. After the socket is ready, **`await session.send.listHistory({})`** (or `{ limit: 100 }`) and render **`messages`**, then **`await session.send.listPresence({})`** to show **who is online** (compare **`selfUserId`** to highlight the current user).
89
+ 3. Register **`session.subscribe.on("userJoined" | "userLeft" | "roomMessage" | "historyCleared", …)`** for live updates (merge joins/leaves into your presence UI; on **`historyCleared`**, drop or redraw stored chat lines for that room).
90
+ 4. Send **`await session.send.sendMessage({ text: "…" })`**. Optional: **`await session.send.clearHistory({})`** to wipe persisted messages for the room (server should **`broadcastPush("historyCleared", …)`** so every client updates).
91
+
92
+ **Minimal client**
53
93
 
54
94
  ```ts
55
95
  import { SockaSession } from "@firtoz/socka/client";
56
- import { myContract } from "./contract";
96
+ import { chatContract } from "./contract";
57
97
 
58
98
  const session = new SockaSession({
59
- contract: myContract,
60
- url: "ws://localhost:3450/ws",
99
+ contract: chatContract,
100
+ url: "ws://localhost:3464/ws/lobby?name=Ada",
61
101
  });
62
- const { text } = await session.send.echo({ text: "hello" });
102
+
103
+ session.subscribe.on("userJoined", (p) => console.log("in", p.displayName));
104
+ session.subscribe.on("userLeft", (p) => console.log("out", p.displayName));
105
+ session.subscribe.on("roomMessage", (m) => console.log(`${m.displayName}: ${m.text}`));
106
+ session.subscribe.on("historyCleared", (p) =>
107
+ console.log("cleared by", p.clearedByDisplayName),
108
+ );
109
+
110
+ const { messages } = await session.send.listHistory({ limit: 50 });
111
+ for (const m of messages) console.log(`[hist] ${m.displayName}: ${m.text}`);
112
+
113
+ const { selfUserId, users } = await session.send.listPresence({});
114
+ console.log("online", selfUserId, users);
115
+
116
+ await session.send.sendMessage({ text: "hello" });
63
117
  ```
64
118
 
65
- ## What socka is
119
+ **Multiple rooms on one page** — use **one `SockaSession` per room** (see the chat example **`public/index.html`** + **`src/client.ts`**): each pane builds its own URL and keeps its own subscriptions.
66
120
 
67
- **Socka** is the library; the **npm package name is [`@firtoz/socka`](https://www.npmjs.com/package/@firtoz/socka)** (scoped). It is **schema-first WebSocket RPC**: one **`defineSocka`** contract gives you typed **`session.send.*`** in the browser and **`handlers`** on the server, with Socka **v1** frames on the wire.
121
+ By default, **`wireFormat`** is JSON see **[Reference](./reference.md#wire-encoding-json-and-msgpack)** if you use **`msgpack`**.
68
122
 
69
- For frame shapes and options, see **[Reference](./reference.md)**.
123
+ ---
70
124
 
71
- ## Other runtimes
125
+ ## Step 4 — Server behavior (all runtimes)
72
126
 
73
- Pick **how** the socket is upgraded, then use the matching subpath:
127
+ For each **`SockaWebSocketSessionConfig`** / **`SockaDoSessionConfig`**:
74
128
 
75
- | You want to… | Read this first | Import path |
76
- |--------------|-----------------|-------------|
77
- | **Node** + **`ws`**, or any standard **`WebSocket`** after upgrade | **[Server](./server.md)** **`attachSockaWebSocket`** | `@firtoz/socka/server` |
78
- | **Bun** **`Bun.serve`** / **`ServerWebSocket`** | **[Server](./server.md)** **`@firtoz/socka/bun`** | `@firtoz/socka/bun` |
79
- | **Hono** on **Node** (`@hono/node-ws`) | **[Server](./server.md)** **`sockaHonoNodeWs`** | `@firtoz/socka/hono` |
80
- | **Hono** on **Cloudflare Workers** | **[Server](./server.md)** **`sockaHonoCloudflare`** | `@firtoz/socka/hono/cloudflare` |
81
- | **Cloudflare Durable Objects** | **[Durable Objects](./durable-objects.md)** | `@firtoz/socka/do` |
129
+ 1. **`createData`** Parse **`roomId`** from the upgrade URL (path) and **`displayName`** from **`name`** query; set **`userId`** (e.g. **`crypto.randomUUID()`**). Same shape as the README **`SockaWebSocketInit`** / Hono **`Context`** on DO.
130
+ 2. **`onAttached`** — `await session.broadcastPush("userJoined", { userId, displayName }, true)` (**`excludeSelf: true`** so only peers see the join).
131
+ 3. **`handlers.listHistory`** Read from your store for **`session.data.roomId`** (Bun: SQLite; Hono: JSON file; DO: SQLite in the object).
132
+ 4. **`handlers.listPresence`** Walk the room’s **`sessionMap`** (or DO **`this.sessions`**) and return **`{ selfUserId: session.data.userId, users: [{ userId, displayName }, …] }`** sorted for display.
133
+ 5. **`handlers.sendMessage`** Persist the line, then **`await session.broadcastPush("roomMessage", row)`** (everyone in the room, including the sender, unless you choose **`excludeSelf`**).
134
+ 6. **`handlers.clearHistory`** Delete persisted messages for **`session.data.roomId`**, then **`await session.broadcastPush("historyCleared", { ts, clearedByUserId, clearedByDisplayName })`** so all clients refresh their UI.
135
+ 7. **`handleClose`** — `await session.broadcastPush("userLeft", { userId, displayName: session.data.displayName }, true)` — **`displayName`** is the **session’s name at disconnect** (same field as in **`userJoined`** / messages), so clients can render a leave line without guessing from **`userId`** alone. Session is still in **`sessions`** until **`handleClose`** finishes — see **[Lifecycle](./lifecycle.md)**.
82
136
 
83
- **Multiple rooms or scopes?** See **[Multi-room](./multi-room.md)**.
137
+ ---
84
138
 
85
- ## Install
139
+ ## Step 5 — Wire the server by runtime
86
140
 
87
- ```bash
88
- npm install @firtoz/socka
89
- ```
141
+ Pick a row, then follow the numbered steps in that subsection.
90
142
 
91
- Add **only** the peers for the subpaths you import—**[Peers](./peers.md)**.
143
+ | Runtime | Install | Full example |
144
+ |---------|---------|--------------|
145
+ | **Bun** | `npm install @firtoz/socka` (+ **`bun-types`** dev if needed) | [chatroom-bun](../../../examples/chatroom-bun) |
146
+ | **Node + `ws`** | `npm install @firtoz/socka ws` (+ **`@types/ws`** dev) | Same contract; attach pattern below |
147
+ | **Hono (Node)** | `npm install @firtoz/socka hono @hono/node-ws @hono/node-server ws` | [chatroom-hono](../../../examples/chatroom-hono) |
148
+ | **Hono (Workers)** | `npm install @firtoz/socka hono` | **[Server](./server.md#firtoz-socka-hono-cloudflare-workers)** — usually **`sockaHonoCloudflare`**; session often starts on first message |
149
+ | **Durable Objects** | `npm install @firtoz/socka hono @firtoz/websocket-do` | [chatroom-do](../../../examples/chatroom-do) |
92
150
 
93
- ## Shared contract
151
+ More installs: **[Peers](./peers.md)**. Cloudflare typings: **`wrangler types`**.
94
152
 
95
- Use one module for client and server (same as the README):
153
+ ### Bun (`Bun.serve`)
96
154
 
97
- ```ts
98
- import { defineSocka } from "@firtoz/socka/core";
99
- import * as z from "zod";
155
+ 1. **Open** a **`Database`** from **`bun:sqlite`** (one file; table keyed by **`room_id`**), **`CREATE TABLE IF NOT EXISTS`** for messages.
156
+ 2. **`getOrCreateRoom(roomId)`** returns **`{ sessionMap, config }`** where **`config`** closes over **`roomId`** and **`db`**.
157
+ 3. **`createSockaBunWebSocketHandlers({ resolveScope })`** **`resolveScope(ws)`** reads **`ws.data.roomId`** (set in **`fetch`** via **`server.upgrade(req, { data: { roomId } })`**).
158
+ 4. **`fetch`** upgrades **`/ws/:roomId`** (decode the segment).
100
159
 
101
- export const myContract = defineSocka({
102
- calls: {
103
- echo: {
104
- input: z.object({ text: z.string() }),
105
- output: z.object({ text: z.string() }),
106
- },
107
- },
108
- });
109
- ```
160
+ ### Node + `ws`
110
161
 
111
- For richer examples (list/insert, optional inputs), see **[Server](./server.md)** and the tic-tac-toe apps under `examples/`.
162
+ 1. **`new WebSocketServer({ port })`**.
163
+ 2. On **`connection`**, parse **`roomId`** from **`req.url`**, **`getOrCreateRoom`**, then **`attachSockaWebSocket( ws, room.sessionMap, room.config, { request: req } )`** so **`createData`** sees the URL.
112
164
 
113
- ## Wire the server, then the client
165
+ ### Hono on Node
114
166
 
115
- You already have the **client** shape (`SockaSession` with the same contract). Now:
167
+ 1. **`createNodeWebSocket({ app })`** from **`@hono/node-ws`**.
168
+ 2. **`app.get("/ws/:roomId", upgradeWebSocket((c) => { const room = getOrCreateRoom(c.req.param("roomId")); return sockaHonoNodeWs(room.config, { sessions: room.sessionMap })(c); }))`**.
169
+ 3. **`serve`** + **`injectWebSocket(server)`**.
116
170
 
117
- 1. **Server** — Open the guide from **Other runtimes** and implement **`handlers`** + **`handleClose`** for **`myContract`**. Use **`SockaWebSocketSession`** / **`attachSockaWebSocket`** (Node/Bun/Hono) or **`SockaDoSession`** / **`SockaWebSocketDO`** (Durable Objects).
118
- 2. **Client** — Keep **`SockaSession`** (or **`useSockaSession`** / **`SockaSessionProvider`**—**[Client](./client.md)**) with the **same** **`wireFormat`** as the server.
171
+ ### Hono on Cloudflare Workers
119
172
 
120
- ### Wire format (short)
173
+ 1. Use **`upgradeWebSocket`** from **`hono/cloudflare-workers`** with **`sockaHonoCloudflare`** — see **[Server](./server.md)**.
174
+ 2. For **room routing** without a DO, put **`roomId`** in the path and parse it in **`createData`** from **`init.request`**.
121
175
 
122
- Default is **JSON text** frames. **`wireFormat: "msgpack"`** must match on **both** sides. Details: **[Reference — Wire encoding](./reference.md#wire-encoding-json-and-msgpack)**.
176
+ ### Cloudflare Durable Objects
123
177
 
124
- ## Run a full-stack demo
178
+ 1. **Worker** route **`/ws/:roomId`** to **`env.CHAT_ROOM.idFromName(roomId).get(id).fetch(...)`** (stub forwards WebSocket upgrade to the DO).
179
+ 2. **DO class** — extend **`SockaWebSocketDO`**; **`SockaDoSession`** handlers use **Drizzle** on **`drizzle(ctx.storage)`** (see [chatroom-do](../../../examples/chatroom-do)).
180
+ 3. **One DO instance per room** — history lives in that object’s SQLite; no **`room_id`** column needed if the table is per-DO.
125
181
 
126
- Same **tic-tac-toe** contract and game logic, three servers in this repo:
182
+ ---
127
183
 
128
- | Stack | Folder | Port |
129
- |-------|--------|------|
130
- | **Bun** | [`tic-tac-toe-bun`](../../../examples/tic-tac-toe-bun) | **3461** |
131
- | **Hono + Node** | [`tic-tac-toe-hono`](../../../examples/tic-tac-toe-hono) | **3462** |
132
- | **Durable Objects** | [`tic-tac-toe-do`](../../../examples/tic-tac-toe-do) | **3463** |
184
+ ## Full-stack examples (chat + tic-tac-toe)
133
185
 
134
- From the folder: **`bun run dev`**. The DO example uses **`wrangler dev`**.
186
+ | Topic | Stack | Folder | Port |
187
+ |-------|--------|--------|------|
188
+ | Chat + history | **Bun** + SQLite | [`chatroom-bun`](../../../examples/chatroom-bun) | **3464** |
189
+ | Chat + history | **Hono + Node** + JSON | [`chatroom-hono`](../../../examples/chatroom-hono) | **3465** |
190
+ | Chat + history | **DO** + Drizzle SQLite | [`chatroom-do`](../../../examples/chatroom-do) | **3466** |
191
+ | Tic-tac-toe | **Bun** | [`tic-tac-toe-bun`](../../../examples/tic-tac-toe-bun) | **3461** |
192
+ | Tic-tac-toe | **Hono + Node** | [`tic-tac-toe-hono`](../../../examples/tic-tac-toe-hono) | **3462** |
193
+ | Tic-tac-toe | **DO** | [`tic-tac-toe-do`](../../../examples/tic-tac-toe-do) | **3463** |
135
194
 
136
195
  ---
137
196
 
138
- Next: [Peers](./peers.md) · [Server](./server.md) · [Durable Objects](./durable-objects.md) · [Client](./client.md) · [Reference](./reference.md)
197
+ Next: [Peers](./peers.md) · [Multi-room](./multi-room.md) · [Server](./server.md) · [Durable Objects](./durable-objects.md) · [Client](./client.md) · [Reference](./reference.md) · [Internals](./internals.md)
@@ -0,0 +1,26 @@
1
+ # History (pagination, retention, invalidation)
2
+
3
+ Long-lived rooms often keep a **message log** on the server. Patterns that work well with socka:
4
+
5
+ ## Pagination / cursor
6
+
7
+ Expose an RPC such as **`listHistory({ limit?, before? })`** where **`before`** is an opaque cursor (e.g. oldest **`ts`** or **`id`** already shown). Return **`messages`** newest-first or oldest-first consistently, and document which end **`before`** anchors.
8
+
9
+ Clients load an initial page after connect, then **prepend** older pages when the user scrolls up.
10
+
11
+ ## Retention
12
+
13
+ Enforce **max rows per room** or **time-based pruning** in the handler that **writes** history (e.g. after **`sendMessage`**). Truncation stays a **server policy**; clients learn about bulk wipes via a **push**.
14
+
15
+ ## `historyCleared` (or equivalent)
16
+
17
+ When one client clears history for everyone, **mutate storage** then **`broadcastPush("historyCleared", { ts, … })`**. Other clients should **drop local message lists** (or refetch **`listHistory`**) so UIs stay consistent.
18
+
19
+ ## Reconnect
20
+
21
+ After a reconnect, **re-run** **`listHistory`** (or your snapshot RPC) — see **[Reconnection](./reconnection.md)**.
22
+
23
+ ## See also
24
+
25
+ - **[Getting started](./getting-started.md)** — chat flow.
26
+ - **[Pushes](./pushes.md)** — broadcasting invalidation events.
@@ -0,0 +1,56 @@
1
+ # Library internals
2
+
3
+ This page is for **contributors** and readers who want the **wire protocol** and implementation edges. If you only need to **use** socka, start with **[Getting started](./getting-started.md)** and **[Reference](./reference.md)**.
4
+
5
+ **Source (monorepo paths):**
6
+
7
+ - Logical frames and **`decodeSockaWire`** — [`packages/socka/src/core/envelope.ts`](../../src/core/envelope.ts)
8
+ - JSON vs msgpack **transport** (`encodeSockaWire`, **`parseWirePayload`**) — [`packages/socka/src/core/wire-codec.ts`](../../src/core/wire-codec.ts)
9
+ - Inbound dispatch — [`packages/socka/src/server/dispatchSockaInboundMessage.ts`](../../src/server/dispatchSockaInboundMessage.ts)
10
+
11
+ ---
12
+
13
+ ## Wire encoding: JSON and msgpack
14
+
15
+ Socka has two layers: **transport encoding** (how each WebSocket frame is serialized) and **logical frames** (the socka v1 object inside). Both sides must agree on **`wireFormat`** or decoding fails immediately (wrong frame type or parse errors).
16
+
17
+ | `wireFormat` | WebSocket frame | Bytes on the wire |
18
+ |--------------|-----------------|-------------------|
19
+ | **`"json"`** (default) | **Text** — `send(string)` | UTF-8 JSON of the **whole** envelope (one JSON object per frame). Uses **`serializeJson`** / **`deserializeJson`** when set, otherwise `JSON.stringify` / `JSON.parse`. |
20
+ | **`"msgpack"`** | **Binary** — `send(ArrayBuffer)` | [msgpack](https://msgpack.org/) of the same envelope object graph. In the browser, **`SockaWebSocketClient`** sets **`binaryType = "arraybuffer"`** so binary frames decode correctly. |
21
+
22
+ **Rules**
23
+
24
+ - Set **`wireFormat`** to the **same value** on the **client** (`SockaSession` / `SockaWebSocketClient` / `useSockaSession` options) and on **every server session** that talks to that client (`SockaWebSocketSessionConfig`, `SockaDoSessionConfig`, and the `config` passed to **`createSockaBunWebSocketHandlers`**, **`sockaHonoNodeWs`**, **`sockaHonoCloudflare`**, etc.).
25
+ - **RPCs and contract pushes** share one encoding: `clientRequest` / `serverResponse` / `serverError` / `serverEvent` are all wrapped the same way.
26
+ - If you use **`dispatchSockaInboundMessage`** manually, pass the same **`wireFormat`** as the peer used to **encode** the frame.
27
+ - Optional **`serializeJson`** / **`deserializeJson`** on client or server config only affect **JSON mode** (the outer envelope). Call **`body`** and push **`body`** values are still whatever your **Standard Schema** accepts after JSON/msgpack decode.
28
+
29
+ ---
30
+
31
+ ## Logical frames (socka v1)
32
+
33
+ Every decoded payload is one logical socka **v1** object. **`decodeSockaWire`** checks shape after `JSON.parse` (text) or msgpack unpack (binary).
34
+
35
+ | Kind | Role |
36
+ |------|------|
37
+ | `clientRequest` | Client → server RPC (`id`, `rpc`, `body`) |
38
+ | `serverResponse` | Success reply (correlated by `id`) |
39
+ | `serverError` | Correlated failure (`id`, `error` message string) |
40
+ | `serverEvent` | Server push (`event`, `body`) — **not** tied to an RPC `id` |
41
+
42
+ Clients generate **`id`** strings per request; servers echo them on **`serverResponse`** and **`serverError`** so concurrent RPCs never mix results. **`serverEvent`** uses the contract **`pushes`** map and **`session.subscribe`** on the client.
43
+
44
+ ---
45
+
46
+ ## TypeScript: `SockaWebSocketDO` and contract erasure
47
+
48
+ `@firtoz/socka/do` **erases** the contract slot on **`SockaWebSocketDO`** so concrete `defineSocka(...)` contracts stay strict under TypeScript. If a generic base class rejects your session type, keep using **your** contract type from the module where you called **`defineSocka`**—do not expect an unconstrained `SockaContract<SockaContractConfig>` to accept every concrete contract without that erasure.
49
+
50
+ ---
51
+
52
+ ## Inbound path (server)
53
+
54
+ **`attachSockaWebSocket`** uses **`dispatchSockaInboundMessage`** with the same `data` shape as a DOM **`MessageEvent`** (`string`, **`ArrayBuffer`**, **`Blob`**, **`ArrayBufferView`**, or **`Buffer`** on Node/Bun). If you handle **`message`** yourself, call **`dispatchSockaInboundMessage(session, wireFormat, data)`** with matching **`wireFormat`**.
55
+
56
+ See also [Reference](./reference.md) for **`SockaWebSocketSessionConfig`** fields and [Server](./server.md) for adapters.
package/docs/lifecycle.md CHANGED
@@ -5,21 +5,21 @@ Join, message, and **leave** ordering for socka sessions—whether you use **`@f
5
5
  ## Registration and `onAttached`
6
6
 
7
7
  1. The adapter accepts or upgrades a **`WebSocket`** and constructs a session (**`SockaWebSocketSession`** or **`SockaDoSession`**).
8
- 2. The session is **registered** in the shared **`sessions`** map (the map **`broadcastContractEvent`** uses).
8
+ 2. The session is **registered** in the shared **`sessions`** map (the map **`broadcastPush`** and peer iteration use).
9
9
  3. On the next microtask, **`onAttached`** runs (if you provided it). Other sessions in the map can see this socket—use this for join broadcasts, not the constructor.
10
10
 
11
11
  If **`onAttached`** throws or returns a rejected promise, the failure is reported via **`reportError`** (or **`console.error`** by default) with kind **`serverOnAttached`**.
12
12
 
13
13
  ## Inbound RPCs
14
14
 
15
- While the socket is open, inbound data is decoded ( **`handleRawMessage`**, **`dispatchSockaInboundMessage`**, or Bun/Hono wrappers), inputs are validated, and **`handlers[procedure]`** runs. Handler exceptions → **`onHandlerError`**; bad wire payloads → **`onValidationError`** before your handler—see **[Reference](./reference.md)**.
15
+ While the socket is open, inbound data is decoded and validated, then **`handlers[procedure]`** runs. Handler exceptions → **`onHandlerError`**; bad wire payloads → **`onValidationError`** before your handler—see **[Reference](./reference.md)**.
16
16
 
17
17
  ## Close and `handleClose`
18
18
 
19
19
  When the transport closes:
20
20
 
21
21
  1. The adapter calls **`await session.invokeHandleClose()`**, which runs **your** **`handleClose(session)`**.
22
- 2. **Until that finishes, the session remains in **`sessions`**—so peer iteration and **`broadcastContractEvent`** can still see the closing peer (e.g. “last player left”).
22
+ 2. **Until that finishes, the session remains in **`sessions`**—so peer iteration and **`broadcastPush`** can still see the closing peer (e.g. “last player left”).
23
23
  3. Then the adapter removes the socket from the map.
24
24
 
25
25
  **`SockaDoSession`** delegates teardown through **`@firtoz/websocket-do`**; see **[Durable Objects](./durable-objects.md)** for **`BaseSession`** details.
@@ -2,30 +2,32 @@
2
2
 
3
3
  A **room** (channel, game, namespace) is a **scope** where every client shares one **`sessionMap`** and one session **config** (the object you pass to **`attachSockaWebSocket`**, **`sockaHonoNodeWs`**, **`createSockaBunWebSocketHandlers`**, …).
4
4
 
5
- That shared **config** includes **`wireFormat`** (`"json"` or `"msgpack"`). Everyone connecting into that scope must use the same encoding—see **[Reference — Wire encoding](./reference.md#wire-encoding-json-and-msgpack)**.
5
+ If you care about **encoding** (`json` vs `msgpack`), everyone in that scope must use the same **`wireFormat`** see **[Reference](./reference.md#wire-encoding-json-and-msgpack)** (details in **[Internals](./internals.md)**).
6
6
 
7
7
  **Durable Objects** — Often one **Durable Object instance** per room (e.g. **`idFromName(roomId)`**), with one **`sessions`** map per instance. See **[Durable Objects](./durable-objects.md)**.
8
8
 
9
9
  Within a scope:
10
10
 
11
11
  - All **`WebSocket`** instances are keys in the same **`Map<WebSocket, Session>`**.
12
- - **`broadcastContractEvent`** walks that map, so “everyone in this room” means “every session in this scope’s map.”
12
+ - **`broadcastPush`** (and anything else that iterates **`sessions`**) only reaches sockets in **that** map “everyone in this room” means “every session in this scope’s map.”
13
13
  - **`handleClose(session)`** runs when a socket leaves; use **`session.websocket`** and **`session.data`** for cleanup. See **[Lifecycle](./lifecycle.md)** for ordering (your handler runs **before** the socket is removed from the map).
14
14
 
15
15
  ## Choosing a pattern
16
16
 
17
17
  | Runtime | Pattern | When it fits |
18
18
  |--------|---------|----------------|
19
- | **Bun** | **`createSockaBunWebSocketHandlers({ resolveScope })`** | One **`Bun.serve`** `websocket` handler; **`resolveScope(ws)`** returns **`{ sessionMap, config }`**—often from **`ws.data`** set during the HTTP upgrade. |
19
+ | **Bun** | **`createSockaBunWebSocketHandlers({ resolveScope })`** | One **`Bun.serve`** `websocket` handler; **`resolveScope(ws)`** returns **`{ sessionMap, config }`**—often **`registry.get(roomId)`** from **`createSockaRoomRegistry`** plus **`ws.data`** from the HTTP upgrade. |
20
20
  | **Hono (Node)** | **A)** One route per room (`/ws/:roomId`) with **`getOrCreateRoom`** and **`sockaHonoNodeWs(room.config, { sessions: room.sessionMap })`**. **B)** Single upgrade route + **`resolveScope(c)`** returning **`{ sessions, config }`**. |
21
21
  | **Durable Objects** | **One DO instance per room** via **`idFromName(roomId)`** (or similar). Each instance has its own **`sessions`** map. |
22
22
 
23
- Demos: [`tic-tac-toe-bun`](../../../examples/tic-tac-toe-bun), [`tic-tac-toe-hono`](../../../examples/tic-tac-toe-hono), [`tic-tac-toe-do`](../../../examples/tic-tac-toe-do).
23
+ **Chat + persisted history (good multi-room reference):** [`chatroom-bun`](../../../examples/chatroom-bun) (SQLite), [`chatroom-hono`](../../../examples/chatroom-hono) (JSON files), [`chatroom-do`](../../../examples/chatroom-do) (Durable Object SQLite). **Games:** [`tic-tac-toe-bun`](../../../examples/tic-tac-toe-bun), [`tic-tac-toe-hono`](../../../examples/tic-tac-toe-hono), [`tic-tac-toe-do`](../../../examples/tic-tac-toe-do).
24
24
 
25
- ## Pitfalls
25
+ If you persist messages **per room**, keep storage keyed by **room** (or one DO per room) so history cannot leak across scopes.
26
26
 
27
- - **Mixing rooms in one map** — Two logical rooms sharing a **`sessionMap`** leak broadcasts and presence. Partition maps per room or use separate DO instances.
28
- - **Stale `config`** — Handlers close over **`config`**; mutating shared objects inside it affects every connection using that config. Prefer immutable snapshots or room-scoped instances (e.g. one **`Game`** per room).
29
- - **Very large rooms on a Durable Object** — One DO is one isolate; huge fan-in can hit limits. Shard by room id (multiple DOs) if needed.
27
+ ## Pitfalls (for app authors)
28
+
29
+ - **Mixing rooms in one map** — If two logical rooms share a **`sessionMap`**, **`broadcastPush`** and “who’s online” can leak across rooms. Give each room its own map (or its own DO instance).
30
+ - **Mutating shared `config`** — Handlers close over **`config`**; changing a shared object inside it affects every connection using that config. Prefer immutable snapshots or a **per-room** config instance (e.g. one **`Game`** object per room).
31
+ - **Very large rooms on a Durable Object** — One DO is one isolate; huge fan-in can hit CPU or memory limits. Split traffic across multiple DOs (e.g. shard by room id) if needed.
30
32
 
31
33
  See also [Lifecycle](./lifecycle.md) and [Server](./server.md) for **`createData`** and **`session.data`**.
package/docs/peers.md CHANGED
@@ -41,15 +41,19 @@ Add **`bun-types`** as a dev dependency for TypeScript if you type-check Bun API
41
41
  ### Cloudflare Workers + Hono upgrade (`@firtoz/socka/hono/cloudflare`)
42
42
 
43
43
  ```bash
44
- npm install @firtoz/socka hono @cloudflare/workers-types
44
+ npm install @firtoz/socka hono
45
45
  ```
46
46
 
47
+ For **TypeScript** on Workers, run **`wrangler types`** (or your app’s **`cf-typegen`** / equivalent) so globals and bindings match your Worker — see [Cloudflare’s TypeScript guide](https://developers.cloudflare.com/workers/languages/typescript). The legacy **`@cloudflare/workers-types`** package still exists and **`@firtoz/socka`** lists it as an **optional** peer for compatibility, but **generated types from your Wrangler config are preferred**.
48
+
47
49
  ### Cloudflare Durable Objects (`@firtoz/socka/do`)
48
50
 
49
51
  ```bash
50
- npm install @firtoz/socka hono @firtoz/websocket-do @cloudflare/workers-types
52
+ npm install @firtoz/socka hono @firtoz/websocket-do
51
53
  ```
52
54
 
55
+ Use **`wrangler types`** (or your project’s typegen) for Worker/DO globals — same as above. **`@cloudflare/workers-types`** is optional if you are not using generated types yet.
56
+
53
57
  **Version pairing:** `@firtoz/socka/do` subclasses **`@firtoz/websocket-do`** (`BaseSession`, `BaseWebSocketDO`). The two packages use **different** version numbers on npm—there is no rule like “same major as socka.” Use a **websocket-do** version that **socka**’s **`peerDependencies`** (and changelog, if you hit edge cases) allow for your **socka** release. You can upgrade **either** package on its own while the integration stays compatible; coordinate when **`BaseSession` / `BaseWebSocketDO`** or socka’s DO layer changes (often **TypeScript** errors first).
54
58
 
55
59
  ### Portable `ws` / `attachSockaWebSocket` only (`@firtoz/socka/server`)
@@ -62,14 +66,14 @@ Add **`@types/ws`** as a dev dependency when you use **`ws`** on Node. (Omit **`
62
66
 
63
67
  ---
64
68
 
65
- `@firtoz/websocket-do` is marked **optional** in **`@firtoz/socka`’s** `package.json` so browser-only clients do not pull Durable Object code. **`@cloudflare/workers-types`** is also **optional** unless you import **`@firtoz/socka/do`** or **`@firtoz/socka/hono/cloudflare`**, where Workers globals are part of the story.
69
+ `@firtoz/websocket-do` is marked **optional** in **`@firtoz/socka`’s** `package.json` so browser-only clients do not pull Durable Object code.
66
70
 
67
71
  **Any Worker that imports `@firtoz/socka/do` must add `@firtoz/websocket-do` explicitly:** `npm install @firtoz/websocket-do`. Choose a version that **satisfies socka’s stated peer range** (and your app’s lockfile); you do not need one-off “lockstep” bumps for every unrelated release—only when integration or types break.
68
72
 
69
73
  ## Practical notes
70
74
 
71
- - **Only install peers for paths you use.** A Vite SPA that only imports `@firtoz/socka/client` does not need `hono`, **`@cloudflare/workers-types`**, or `@firtoz/websocket-do`.
72
- - **TypeScript:** For Workers code, add **`@cloudflare/workers-types`** to `compilerOptions.types` (or use your framework’s defaults). For **`@firtoz/socka/bun`** handlers, add **`bun-types`** when you author against Bun APIs.
75
+ - **Only install peers for paths you use.** A Vite SPA that only imports `@firtoz/socka/client` does not need `hono` or `@firtoz/websocket-do`.
76
+ - **TypeScript on Cloudflare:** Prefer **`wrangler types`** output (or your framework’s generated **`Env`**) over manually installing **`@cloudflare/workers-types`** alone — generated types follow your **bindings** and **compatibility date**. For **`@firtoz/socka/bun`** handlers, add **`bun-types`** when you author against Bun APIs.
73
77
  - **socka + websocket-do:** If **`@firtoz/websocket-do`** is **outside** what your **socka** version expects (or websocket-do ships a breaking `BaseSession` / `BaseWebSocketDO` change), you may see **type errors** on `SockaDoSession` / `SockaWebSocketDO` or runtime issues—then bump **one or both** until the pairing in the docs / peer range works again.
74
78
 
75
79
  ## By entrypoint (reference)
@@ -78,8 +82,8 @@ Add **`@types/ws`** as a dev dependency when you use **`ws`** on Node. (Omit **`
78
82
  |--------|----------------|-----|
79
83
  | `@firtoz/socka/core`, `@firtoz/socka/client` | **None** | Uses Standard Schema, **`WebSocket`**, and shared frame types—**`lib: ["DOM"]`** (or your bundler defaults) is enough. |
80
84
  | `@firtoz/socka/react` | `react` **≥ 18** | Hooks and provider API. |
81
- | `@firtoz/socka/do` | **`@firtoz/websocket-do`** (version range per **socka** `peerDependencies` / changelog), **`@cloudflare/workers-types`**, **`hono`** | `SockaDoSession` extends **`BaseSession`** from **websocket-do**; **`SockaWebSocketDO`** uses **Hono**-shaped routing on top of **`BaseWebSocketDO`**. |
85
+ | `@firtoz/socka/do` | **`@firtoz/websocket-do`** (version range per **socka** `peerDependencies` / changelog), **`hono`** | `SockaDoSession` extends **`BaseSession`** from **websocket-do**; **`SockaWebSocketDO`** uses **Hono**-shaped routing on top of **`BaseWebSocketDO`**. Add Cloudflare typings via **`wrangler types`**, not only the generic **`@cloudflare/workers-types`** package. |
82
86
  | `@firtoz/socka/server` | None beyond `@firtoz/socka/core` (standard **`WebSocket`** + same contract types) | Portable **`attachSockaWebSocket`** path. |
83
87
  | `@firtoz/socka/bun` | Same as `@firtoz/socka/server` (**`bun-types`** for TypeScript) | **`Bun.serve`** / **`ServerWebSocket`** integration. |
84
88
  | `@firtoz/socka/hono` | **`hono`**, **`@hono/node-ws`**, **`@hono/node-server`**, **`ws`** (runtime + types) | Node **`upgradeWebSocket`** pipeline. |
85
- | `@firtoz/socka/hono/cloudflare` | **`hono`**, **`@cloudflare/workers-types`** (**`upgradeWebSocket`** from `hono/cloudflare-workers`) | Workers WebSocket upgrade (session often starts on first message—see **[Server](./server.md)**). |
89
+ | `@firtoz/socka/hono/cloudflare` | **`hono`** | Workers WebSocket upgrade via **`hono/cloudflare-workers`** (session often starts on first message—see **[Server](./server.md)**). Use **`wrangler types`** for Worker globals. |