@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.
- package/README.md +191 -40
- package/dist/{SockaWebSocketSession-Bru8yFcK.d.ts → SockaWebSocketSession-Cza7Fti-.d.ts} +87 -5
- package/dist/bun/index.d.ts +28 -3
- package/dist/bun/index.js +28 -5
- package/dist/bun/index.js.map +1 -1
- package/dist/{chunk-MZCQHJXY.js → chunk-2FNWVCP3.js} +27 -8
- package/dist/chunk-2FNWVCP3.js.map +1 -0
- package/dist/{chunk-AM7PB26G.js → chunk-H3S3435J.js} +125 -3
- package/dist/chunk-H3S3435J.js.map +1 -0
- package/dist/{chunk-45D4T232.js → chunk-JVLUA3Q5.js} +64 -6
- package/dist/chunk-JVLUA3Q5.js.map +1 -0
- package/dist/chunk-KQO5AVKA.js +8 -0
- package/dist/chunk-KQO5AVKA.js.map +1 -0
- package/dist/client/index.d.ts +59 -3
- package/dist/client/index.js +2 -2
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +1 -1
- package/dist/do/index.d.ts +20 -2
- package/dist/do/index.js +35 -2
- package/dist/do/index.js.map +1 -1
- package/dist/hono/cloudflare-workers.d.ts +2 -2
- package/dist/hono/cloudflare-workers.js +4 -3
- package/dist/hono/cloudflare-workers.js.map +1 -1
- package/dist/hono/index.d.ts +20 -4
- package/dist/hono/index.js +5 -3
- package/dist/hono/index.js.map +1 -1
- package/dist/react/index.d.ts +43 -4
- package/dist/react/index.js +103 -9
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.d.ts +17 -4
- package/dist/server/index.js +24 -4
- package/dist/server/index.js.map +1 -1
- package/dist/{socka-report-error-DzFI2Tr7.d.ts → socka-report-error-ixTynx4w.d.ts} +8 -1
- package/dist/test/index.d.ts +11 -0
- package/dist/test/index.js +84 -0
- package/dist/test/index.js.map +1 -0
- package/docs/README.md +15 -6
- package/docs/auth.md +27 -0
- package/docs/backpressure.md +16 -0
- package/docs/client.md +44 -3
- package/docs/comparison.md +1 -1
- package/docs/durable-objects.md +2 -2
- package/docs/getting-started.md +143 -84
- package/docs/history.md +26 -0
- package/docs/internals.md +56 -0
- package/docs/lifecycle.md +3 -3
- package/docs/multi-room.md +10 -8
- package/docs/peers.md +11 -7
- package/docs/presence.md +43 -0
- package/docs/{events.md → pushes.md} +1 -1
- package/docs/recipes.md +78 -0
- package/docs/reconnection.md +44 -0
- package/docs/reference.md +21 -30
- package/docs/server.md +14 -1
- package/docs/testing.md +20 -0
- package/docs/wire-format.md +25 -0
- package/examples/minimal-socka.ts +56 -3
- package/package.json +14 -10
- package/dist/chunk-45D4T232.js.map +0 -1
- package/dist/chunk-AM7PB26G.js.map +0 -1
- 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
|
|
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](./
|
|
126
|
+
Server push and client subscriptions are covered in **[Pushes](./pushes.md)**.
|
package/docs/comparison.md
CHANGED
|
@@ -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 **[
|
|
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.
|
package/docs/durable-objects.md
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
package/docs/getting-started.md
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
# Getting started
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
28
|
-
import { createSockaBunWebSocketHandlers } from "@firtoz/socka/bun";
|
|
29
|
-
import { myContract } from "./contract";
|
|
38
|
+
export type ChatMessageRow = z.infer<typeof messageRow>;
|
|
30
39
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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 {
|
|
96
|
+
import { chatContract } from "./contract";
|
|
57
97
|
|
|
58
98
|
const session = new SockaSession({
|
|
59
|
-
contract:
|
|
60
|
-
url: "ws://localhost:
|
|
99
|
+
contract: chatContract,
|
|
100
|
+
url: "ws://localhost:3464/ws/lobby?name=Ada",
|
|
61
101
|
});
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
By default, **`wireFormat`** is JSON — see **[Reference](./reference.md#wire-encoding-json-and-msgpack)** if you use **`msgpack`**.
|
|
68
122
|
|
|
69
|
-
|
|
123
|
+
---
|
|
70
124
|
|
|
71
|
-
##
|
|
125
|
+
## Step 4 — Server behavior (all runtimes)
|
|
72
126
|
|
|
73
|
-
|
|
127
|
+
For each **`SockaWebSocketSessionConfig`** / **`SockaDoSessionConfig`**:
|
|
74
128
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
137
|
+
---
|
|
84
138
|
|
|
85
|
-
##
|
|
139
|
+
## Step 5 — Wire the server by runtime
|
|
86
140
|
|
|
87
|
-
|
|
88
|
-
npm install @firtoz/socka
|
|
89
|
-
```
|
|
141
|
+
Pick a row, then follow the numbered steps in that subsection.
|
|
90
142
|
|
|
91
|
-
|
|
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
|
-
|
|
151
|
+
More installs: **[Peers](./peers.md)**. Cloudflare typings: **`wrangler types`**.
|
|
94
152
|
|
|
95
|
-
|
|
153
|
+
### Bun (`Bun.serve`)
|
|
96
154
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
### Hono on Node
|
|
114
166
|
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
+
### Cloudflare Durable Objects
|
|
123
177
|
|
|
124
|
-
|
|
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
|
-
|
|
182
|
+
---
|
|
127
183
|
|
|
128
|
-
|
|
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
|
-
|
|
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)
|
package/docs/history.md
ADDED
|
@@ -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 **`
|
|
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
|
|
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 **`
|
|
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.
|
package/docs/multi-room.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
- **`
|
|
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`**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
- **
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
72
|
-
- **TypeScript:**
|
|
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),
|
|
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
|
|
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. |
|