@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/README.md
CHANGED
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
-
**Typed WebSocket RPC for TypeScript.**
|
|
13
|
+
**Typed WebSocket RPC and pushes for TypeScript.** One **`defineSocka`** contract gives you **`session.send.*`** for RPCs and **`session.subscribe`** for **typed server pushes**—validated, correlated, same schema on client and server.
|
|
14
14
|
|
|
15
15
|
**npm:** [`@firtoz/socka`](https://www.npmjs.com/package/@firtoz/socka). *Socka* is the project name in prose; **install and `import` paths always use `@firtoz/socka` or `@firtoz/socka/...`**. The published artifact is **compiled ESM + `.d.ts` in `dist/`** (see `package.json` `exports`).
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Minimal example: multi-room chat (Bun)
|
|
18
|
+
|
|
19
|
+
Join/leave and live messages use **`pushes`**; persisted lines use **`listHistory`**; who is connected uses **`listPresence`**; **`clearHistory`** wipes stored messages and **`historyCleared`** notifies the room (examples also show presence in the UI). This snippet keeps **history in memory** so it stays short—see **[chatroom-bun](../../examples/chatroom-bun)** for **SQLite**, **[chatroom-hono](../../examples/chatroom-hono)** for **file JSON**, and **[chatroom-do](../../examples/chatroom-do)** for **Durable Object SQLite**.
|
|
18
20
|
|
|
19
21
|
**`contract.ts`** (shared):
|
|
20
22
|
|
|
@@ -22,35 +24,158 @@
|
|
|
22
24
|
import { defineSocka } from "@firtoz/socka/core";
|
|
23
25
|
import * as z from "zod";
|
|
24
26
|
|
|
25
|
-
export const
|
|
27
|
+
export const messageRow = z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
ts: z.number(),
|
|
30
|
+
userId: z.string(),
|
|
31
|
+
displayName: z.string(),
|
|
32
|
+
text: z.string(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type ChatMessageRow = z.infer<typeof messageRow>;
|
|
36
|
+
|
|
37
|
+
const onlineUser = z.object({
|
|
38
|
+
userId: z.string(),
|
|
39
|
+
displayName: z.string(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const chatContract = defineSocka({
|
|
26
43
|
calls: {
|
|
27
|
-
|
|
28
|
-
input: z.object({
|
|
29
|
-
output: z.object({
|
|
44
|
+
listHistory: {
|
|
45
|
+
input: z.object({ limit: z.number().int().min(1).max(500).optional() }),
|
|
46
|
+
output: z.object({ messages: z.array(messageRow) }),
|
|
47
|
+
},
|
|
48
|
+
listPresence: {
|
|
49
|
+
input: z.object({}).optional(),
|
|
50
|
+
output: z.object({
|
|
51
|
+
selfUserId: z.string(),
|
|
52
|
+
users: z.array(onlineUser),
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
sendMessage: {
|
|
56
|
+
input: z.object({ text: z.string().min(1) }),
|
|
57
|
+
output: z.object({ ok: z.literal(true) }),
|
|
58
|
+
},
|
|
59
|
+
clearHistory: {
|
|
60
|
+
input: z.object({}).optional(),
|
|
61
|
+
output: z.object({ ok: z.literal(true) }),
|
|
30
62
|
},
|
|
31
63
|
},
|
|
64
|
+
pushes: {
|
|
65
|
+
userJoined: z.object({ userId: z.string(), displayName: z.string() }),
|
|
66
|
+
userLeft: z.object({
|
|
67
|
+
userId: z.string(),
|
|
68
|
+
displayName: z.string(),
|
|
69
|
+
}),
|
|
70
|
+
roomMessage: messageRow,
|
|
71
|
+
historyCleared: z.object({
|
|
72
|
+
ts: z.number(),
|
|
73
|
+
clearedByUserId: z.string(),
|
|
74
|
+
clearedByDisplayName: z.string(),
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
32
77
|
});
|
|
33
78
|
```
|
|
34
79
|
|
|
35
|
-
**`server.ts
|
|
80
|
+
**`server.ts`** — **`createSockaRoomRegistry`** holds one **`sessionMap` + config per room**; **`strictUpgradeRequest: true`** types **`createData`** with a real **`Request`** (no `http://_/` placeholder). **`session.listPeers()`** replaces hand-rolled **`sessionMap`** walks for presence.
|
|
36
81
|
|
|
37
82
|
```ts
|
|
83
|
+
import type { ServerWebSocket } from "bun";
|
|
38
84
|
import { createSockaBunWebSocketHandlers } from "@firtoz/socka/bun";
|
|
39
|
-
import {
|
|
85
|
+
import {
|
|
86
|
+
createSockaRoomRegistry,
|
|
87
|
+
type SockaStrictWebSocketInit,
|
|
88
|
+
type SockaWebSocketSessionConfig,
|
|
89
|
+
} from "@firtoz/socka/server";
|
|
90
|
+
import { type ChatMessageRow, chatContract } from "./contract";
|
|
91
|
+
|
|
92
|
+
type SessionData = { roomId: string; userId: string; displayName: string };
|
|
93
|
+
|
|
94
|
+
/** In-memory demo store — swap for SQLite / files / DO in real apps. */
|
|
95
|
+
const history = new Map<string, ChatMessageRow[]>();
|
|
96
|
+
|
|
97
|
+
const registry = createSockaRoomRegistry(
|
|
98
|
+
(roomId): SockaWebSocketSessionConfig<typeof chatContract, SessionData> => ({
|
|
99
|
+
contract: chatContract,
|
|
100
|
+
strictUpgradeRequest: true,
|
|
101
|
+
createData: (init: SockaStrictWebSocketInit) => {
|
|
102
|
+
const u = new URL(init.request.url);
|
|
103
|
+
const displayName = u.searchParams.get("name")?.trim() || "anon";
|
|
104
|
+
return { roomId, userId: crypto.randomUUID(), displayName };
|
|
105
|
+
},
|
|
106
|
+
onAttached: async (session) => {
|
|
107
|
+
await session.broadcastPush(
|
|
108
|
+
"userJoined",
|
|
109
|
+
{ userId: session.data.userId, displayName: session.data.displayName },
|
|
110
|
+
true,
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
handlers: {
|
|
114
|
+
listHistory: async (input, session) => {
|
|
115
|
+
const lim = input.limit ?? 200;
|
|
116
|
+
const rows = history.get(session.data.roomId) ?? [];
|
|
117
|
+
return { messages: rows.slice(-lim) };
|
|
118
|
+
},
|
|
119
|
+
listPresence: async (_input, session) => {
|
|
120
|
+
const users = session.listPeers().map((d) => ({
|
|
121
|
+
userId: d.userId,
|
|
122
|
+
displayName: d.displayName,
|
|
123
|
+
}));
|
|
124
|
+
users.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
125
|
+
return { selfUserId: session.data.userId, users };
|
|
126
|
+
},
|
|
127
|
+
sendMessage: async (input, session) => {
|
|
128
|
+
const row = {
|
|
129
|
+
id: crypto.randomUUID(),
|
|
130
|
+
ts: Date.now(),
|
|
131
|
+
userId: session.data.userId,
|
|
132
|
+
displayName: session.data.displayName,
|
|
133
|
+
text: input.text,
|
|
134
|
+
};
|
|
135
|
+
const list = history.get(session.data.roomId) ?? [];
|
|
136
|
+
list.push(row);
|
|
137
|
+
history.set(session.data.roomId, list);
|
|
138
|
+
await session.broadcastPush("roomMessage", row);
|
|
139
|
+
return { ok: true as const };
|
|
140
|
+
},
|
|
141
|
+
clearHistory: async (_input, session) => {
|
|
142
|
+
history.set(session.data.roomId, []);
|
|
143
|
+
const ts = Date.now();
|
|
144
|
+
await session.broadcastPush("historyCleared", {
|
|
145
|
+
ts,
|
|
146
|
+
clearedByUserId: session.data.userId,
|
|
147
|
+
clearedByDisplayName: session.data.displayName,
|
|
148
|
+
});
|
|
149
|
+
return { ok: true as const };
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
handleClose: async (session) => {
|
|
153
|
+
await session.broadcastPush(
|
|
154
|
+
"userLeft",
|
|
155
|
+
{ userId: session.data.userId, displayName: session.data.displayName },
|
|
156
|
+
true,
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
type BunWsData = { roomId: string; request: Request };
|
|
40
163
|
|
|
41
164
|
const { websocket } = createSockaBunWebSocketHandlers({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
165
|
+
resolveScope(ws: ServerWebSocket<BunWsData>) {
|
|
166
|
+
const { roomId } = ws.data;
|
|
167
|
+
const room = registry.get(roomId);
|
|
168
|
+
return { sessionMap: room.sessionMap, config: room.config };
|
|
45
169
|
},
|
|
46
|
-
handleClose: async () => {},
|
|
47
170
|
});
|
|
48
171
|
|
|
49
|
-
Bun.serve({
|
|
172
|
+
Bun.serve<BunWsData>({
|
|
50
173
|
port: 3450,
|
|
51
174
|
fetch(req, server) {
|
|
52
|
-
|
|
53
|
-
|
|
175
|
+
const url = new URL(req.url);
|
|
176
|
+
if (url.pathname.startsWith("/ws/")) {
|
|
177
|
+
const roomId = decodeURIComponent(url.pathname.slice(4)) || "default";
|
|
178
|
+
if (server.upgrade(req, { data: { roomId, request: req } })) return undefined;
|
|
54
179
|
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
55
180
|
}
|
|
56
181
|
return new Response("OK");
|
|
@@ -59,40 +184,63 @@ Bun.serve({
|
|
|
59
184
|
});
|
|
60
185
|
```
|
|
61
186
|
|
|
187
|
+
*Ports: this minimal snippet listens on **3450**; the [full-stack examples](#full-stack-examples) use **3461–3466**.*
|
|
188
|
+
|
|
62
189
|
**`client.ts`** (browser or Bun):
|
|
63
190
|
|
|
64
191
|
```ts
|
|
65
192
|
import { SockaSession } from "@firtoz/socka/client";
|
|
66
|
-
import {
|
|
193
|
+
import { chatContract } from "./contract";
|
|
67
194
|
|
|
68
195
|
const session = new SockaSession({
|
|
69
|
-
contract:
|
|
70
|
-
url: "ws://localhost:3450/ws",
|
|
196
|
+
contract: chatContract,
|
|
197
|
+
url: "ws://localhost:3450/ws/lobby?name=Ada",
|
|
71
198
|
});
|
|
72
|
-
|
|
73
|
-
console.log(
|
|
199
|
+
|
|
200
|
+
session.subscribe.on("userJoined", (p) => console.log("joined", p));
|
|
201
|
+
session.subscribe.on("userLeft", (p) => console.log("left", p.displayName));
|
|
202
|
+
session.subscribe.on("roomMessage", (m) => console.log(`${m.displayName}: ${m.text}`));
|
|
203
|
+
session.subscribe.on("historyCleared", (p) =>
|
|
204
|
+
console.log("history cleared by", p.clearedByDisplayName, "at", p.ts),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const { messages } = await session.send.listHistory({});
|
|
208
|
+
console.log("history", messages);
|
|
209
|
+
const { selfUserId, users } = await session.send.listPresence({});
|
|
210
|
+
console.log("online", selfUserId, users);
|
|
211
|
+
await session.send.sendMessage({ text: "hello room" });
|
|
212
|
+
await session.send.clearHistory({});
|
|
74
213
|
```
|
|
75
214
|
|
|
76
|
-
Run **`bun run server.ts`**, then point the client at **`ws
|
|
215
|
+
Run **`bun run server.ts`**, then point the client at the same **`ws://…/ws/<room>?name=…`** path you upgrade in **`fetch`**.
|
|
216
|
+
|
|
217
|
+
**More examples:** **[chatroom-bun](../../examples/chatroom-bun)** (SQLite + multi-room UI) · **[chatroom-hono](../../examples/chatroom-hono)** · **[chatroom-do](../../examples/chatroom-do)** · tic-tac-toe **[Bun](../../examples/tic-tac-toe-bun)** · **[Hono + Node](../../examples/tic-tac-toe-hono)** · **[Cloudflare DO](../../examples/tic-tac-toe-do)**.
|
|
77
218
|
|
|
78
219
|
## Install
|
|
79
220
|
|
|
80
|
-
|
|
81
|
-
npm install @firtoz/socka
|
|
82
|
-
```
|
|
221
|
+
Always install **`@firtoz/socka`**, then add **only** what your imports need (`npm` / `pnpm` / `bun add` as you prefer):
|
|
83
222
|
|
|
84
|
-
|
|
223
|
+
| You are building… | Install |
|
|
224
|
+
|-------------------|---------|
|
|
225
|
+
| **Browser / Vite SPA** (client only) | `npm install @firtoz/socka` |
|
|
226
|
+
| **React** (`@firtoz/socka/react`) | `npm install @firtoz/socka react` — add **`@types/react`** as a dev dependency if TypeScript asks |
|
|
227
|
+
| **Bun** (`Bun.serve`, `@firtoz/socka/bun`) | `npm install @firtoz/socka` — add **`bun-types`** as a dev dependency if you type-check Bun APIs |
|
|
228
|
+
| **Node + Hono + `@hono/node-ws`** | `npm install @firtoz/socka hono @hono/node-ws @hono/node-server ws` — add **`@types/ws`** as a dev dependency when you use **`ws`** on Node |
|
|
229
|
+
| **Cloudflare Workers + Hono** (`@firtoz/socka/hono/cloudflare`) | `npm install @firtoz/socka hono` |
|
|
230
|
+
| **Cloudflare Durable Objects** (`@firtoz/socka/do`) | `npm install @firtoz/socka hono @firtoz/websocket-do` |
|
|
85
231
|
|
|
86
|
-
|
|
232
|
+
For **Cloudflare TypeScript types**, prefer **`wrangler types`** (or your app’s typegen) so globals and bindings match your Worker — see [Cloudflare’s TypeScript guide](https://developers.cloudflare.com/workers/languages/typescript). More detail: **[Peers](./docs/peers.md)**.
|
|
87
233
|
|
|
88
234
|
## Other runtimes
|
|
89
235
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
|
93
|
-
|
|
94
|
-
| **
|
|
95
|
-
| **
|
|
236
|
+
Pick how the socket is upgraded, then use the matching import path and guide:
|
|
237
|
+
|
|
238
|
+
| Runtime | Import path | Quick start |
|
|
239
|
+
|---------|-------------|-------------|
|
|
240
|
+
| **Node + [`ws`](https://github.com/websockets/ws)** (or any standard **`WebSocket`** after upgrade) | `@firtoz/socka/server` | [`attachSockaWebSocket`](./docs/server.md) |
|
|
241
|
+
| **Bun** (`Bun.serve` / `ServerWebSocket`) | `@firtoz/socka/bun` | [`createSockaBunWebSocketHandlers`](./docs/server.md#firtoz-socka-bun-bunserve) |
|
|
242
|
+
| **Hono on Node** (`@hono/node-ws`) | `@firtoz/socka/hono` | [`sockaHonoNodeWs`](./docs/server.md#firtoz-socka-hono-node-hono-node-ws) |
|
|
243
|
+
| **Hono on Cloudflare Workers** | `@firtoz/socka/hono/cloudflare` | [`sockaHonoCloudflare`](./docs/server.md#firtoz-socka-hono-cloudflare-workers) |
|
|
96
244
|
| **Cloudflare Durable Objects** | `@firtoz/socka/do` | **[Durable Objects](./docs/durable-objects.md)** |
|
|
97
245
|
|
|
98
246
|
## Why not socket.io, tRPC, or DIY?
|
|
@@ -100,6 +248,8 @@ Optional peers depend on which subpath you import—see **[Peers](./docs/peers.m
|
|
|
100
248
|
- **Schema-first RPC + push** — one contract; no parallel “event” protocol for server pushes.
|
|
101
249
|
- **Correlated envelopes** — request/response IDs and validation hooks are built in.
|
|
102
250
|
- **Same contract** across Bun, Hono, Node `ws`, and Durable Objects (see **[Comparison](./docs/comparison.md)** for socket.io / tRPC / hand-rolled).
|
|
251
|
+
- **Room registry + presence helpers** — **`createSockaRoomRegistry`** for per-room **`sessionMap`** / config; **`session.listPeers()`** for who is in the room without walking maps by hand.
|
|
252
|
+
- **Strict upgrade typing + optional reconnect** — Bun/Hono can set **`strictUpgradeRequest: true`** so **`createData`** sees **`init.request`**; **`SockaWebSocketClient`** / **`SockaSession`** can **`reconnect`** with exponential backoff (see **[Reconnection](./docs/reconnection.md)**).
|
|
103
253
|
|
|
104
254
|
## Documentation
|
|
105
255
|
|
|
@@ -109,12 +259,13 @@ Hub: **[`docs/README.md`](./docs/README.md)** (getting started, peers, lifecycle
|
|
|
109
259
|
|
|
110
260
|
## Full-stack examples
|
|
111
261
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
| **
|
|
117
|
-
|
|
|
118
|
-
| **
|
|
262
|
+
| Topic | Stack | Folder | Port |
|
|
263
|
+
|-------|--------|--------|------|
|
|
264
|
+
| **Chat + history** | **Bun** + SQLite | [`chatroom-bun`](../../examples/chatroom-bun) | **3464** |
|
|
265
|
+
| **Chat + history** | **Hono + Node** + JSON files | [`chatroom-hono`](../../examples/chatroom-hono) | **3465** |
|
|
266
|
+
| **Chat + history** | **Cloudflare DO** + Drizzle SQLite | [`chatroom-do`](../../examples/chatroom-do) | **3466** |
|
|
267
|
+
| Tic-tac-toe | **Bun** | [`tic-tac-toe-bun`](../../examples/tic-tac-toe-bun) | **3461** |
|
|
268
|
+
| Tic-tac-toe | **Hono + Node** | [`tic-tac-toe-hono`](../../examples/tic-tac-toe-hono) | **3462** |
|
|
269
|
+
| Tic-tac-toe | **Cloudflare DO** | [`tic-tac-toe-do`](../../examples/tic-tac-toe-do) | **3463** |
|
|
119
270
|
|
|
120
|
-
|
|
271
|
+
Chat apps: **`bun run dev`** (or **`wrangler dev`** for **`chatroom-do`**). Tic-tac-toe: same.
|
|
@@ -1,21 +1,77 @@
|
|
|
1
|
-
import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat, e as InferSockaHandlers, y as SockaReportError, g as InferSockaPushPayload } from './socka-report-error-
|
|
1
|
+
import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat, e as InferSockaHandlers, y as SockaReportError, g as InferSockaPushPayload } from './socka-report-error-ixTynx4w.js';
|
|
2
2
|
|
|
3
3
|
/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */
|
|
4
4
|
type EmptySockaSessionData$1 = Record<string, never>;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Upgrade context passed into `createData` for {@link SockaWebSocketSession} when
|
|
7
|
+
* **`strictUpgradeRequest` is not set to `true`** (the default).
|
|
8
|
+
*
|
|
9
|
+
* **`request` is optional** because some call sites attach a socket without an HTTP upgrade
|
|
10
|
+
* (custom tests, unusual adapters). If you read query params or headers from the upgrade,
|
|
11
|
+
* you must handle a missing `request` (e.g. optional chaining and a fallback URL), or set
|
|
12
|
+
* **`strictUpgradeRequest: true`** instead so {@link SockaStrictWebSocketInit} applies.
|
|
13
|
+
*/
|
|
6
14
|
type SockaWebSocketInit = {
|
|
7
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Original HTTP **`Request`** for the WebSocket upgrade, when the adapter supplies it
|
|
17
|
+
* (e.g. the optional fourth argument to **`attachSockaWebSocket`**).
|
|
18
|
+
*/
|
|
8
19
|
request?: Request;
|
|
9
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Upgrade context when **`strictUpgradeRequest: true`** is set on
|
|
23
|
+
* {@link SockaWebSocketSessionConfig}.
|
|
24
|
+
*
|
|
25
|
+
* **What this enables:** `createData` is typed so **`init.request` is always defined**.
|
|
26
|
+
* You can use **`new URL(init.request.url)`** and read search params without
|
|
27
|
+
* `init.request?.url ?? "http://_/"` placeholders, and TypeScript will catch mistakes if you
|
|
28
|
+
* treat the request as optional.
|
|
29
|
+
*
|
|
30
|
+
* **Runtime behavior:** If the adapter does not pass a `Request` while strict mode is on,
|
|
31
|
+
* socka throws an error explaining how to wire the upgrade (e.g. Bun `data: { request: req }`,
|
|
32
|
+
* or Hono default `sockaInit`).
|
|
33
|
+
*
|
|
34
|
+
* **Typical use:** Bun **`Bun.serve`** upgrades and Hono **`sockaHonoNodeWs`** /
|
|
35
|
+
* **`sockaHonoCloudflare`** where the incoming HTTP request is available and should drive
|
|
36
|
+
* `session.data` (names, cookies, etc.).
|
|
37
|
+
*/
|
|
38
|
+
type SockaStrictWebSocketInit = {
|
|
39
|
+
request: Request;
|
|
40
|
+
};
|
|
10
41
|
type SockaWebSocketCreateData<TData> = [TData] extends [EmptySockaSessionData$1] ? {
|
|
11
42
|
createData?: (init: SockaWebSocketInit) => TData;
|
|
12
43
|
} : {
|
|
13
44
|
createData: (init: SockaWebSocketInit) => TData;
|
|
14
45
|
};
|
|
46
|
+
type SockaWebSocketCreateDataStrict<TData> = [TData] extends [
|
|
47
|
+
EmptySockaSessionData$1
|
|
48
|
+
] ? {
|
|
49
|
+
createData?: (init: SockaStrictWebSocketInit) => TData;
|
|
50
|
+
} : {
|
|
51
|
+
createData: (init: SockaStrictWebSocketInit) => TData;
|
|
52
|
+
};
|
|
15
53
|
type SockaSessionForHandlers<TContract extends SockaContract<SockaContractConfig>, TData> = SockaWebSocketSession<TContract, TData>;
|
|
16
54
|
/**
|
|
17
55
|
* Configuration for {@link SockaWebSocketSession}. Handlers receive the session
|
|
18
56
|
* instance as the second argument (or the only argument when the procedure has no input).
|
|
57
|
+
*
|
|
58
|
+
* ## `strictUpgradeRequest` (optional flag)
|
|
59
|
+
*
|
|
60
|
+
* Controls how **`createData`** is typed and validated for the **HTTP upgrade**:
|
|
61
|
+
*
|
|
62
|
+
* - **Omitted or `undefined` (default)** — `createData` receives {@link SockaWebSocketInit}.
|
|
63
|
+
* **`init.request` may be missing.** Use this for adapters or tests that construct sessions
|
|
64
|
+
* without a real upgrade request, or when you intentionally support both cases and handle
|
|
65
|
+
* optional `request` in code.
|
|
66
|
+
*
|
|
67
|
+
* - **`true`** — Opt in to **strict** upgrade typing: `createData` receives
|
|
68
|
+
* {@link SockaStrictWebSocketInit}, so **`init.request` is required** in TypeScript and
|
|
69
|
+
* enforced at runtime. Prefer this for normal Bun/Hono apps that always have an upgrade
|
|
70
|
+
* `Request`, so you avoid placeholder URLs and get clearer errors if wiring is wrong.
|
|
71
|
+
*
|
|
72
|
+
* Bun and Hono helpers document how they populate init (e.g. **`sockaBunInitFromWsData`**,
|
|
73
|
+
* default **`sockaInit`** from Hono context). The published **Server** guide includes a
|
|
74
|
+
* **Strict upgrade request** section with examples.
|
|
19
75
|
*/
|
|
20
76
|
type SockaWebSocketSessionConfig<TContract extends SockaContract<SockaContractConfig>, TData = EmptySockaSessionData$1> = {
|
|
21
77
|
contract: TContract;
|
|
@@ -38,7 +94,11 @@ type SockaWebSocketSessionConfig<TContract extends SockaContract<SockaContractCo
|
|
|
38
94
|
* (safe to broadcast to peers). Sync or async; async rejections are logged.
|
|
39
95
|
*/
|
|
40
96
|
onAttached?: (session: SockaSessionForHandlers<TContract, TData>) => void | Promise<void>;
|
|
41
|
-
} &
|
|
97
|
+
} & (({
|
|
98
|
+
strictUpgradeRequest?: undefined;
|
|
99
|
+
} & SockaWebSocketCreateData<TData>) | ({
|
|
100
|
+
strictUpgradeRequest: true;
|
|
101
|
+
} & SockaWebSocketCreateDataStrict<TData>));
|
|
42
102
|
|
|
43
103
|
/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */
|
|
44
104
|
type EmptySockaSessionData = Record<string, never>;
|
|
@@ -77,6 +137,28 @@ declare class SockaWebSocketSession<TContract extends SockaContract<SockaContrac
|
|
|
77
137
|
private _data;
|
|
78
138
|
constructor(websocket: WebSocket, sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>, config: SockaWebSocketSessionConfig<TContract, TData>, init?: SockaWebSocketInit);
|
|
79
139
|
get data(): TData;
|
|
140
|
+
/**
|
|
141
|
+
* Session data for every connection in the same {@link sessions} map (same room),
|
|
142
|
+
* optionally excluding this socket.
|
|
143
|
+
*/
|
|
144
|
+
listPeers(options?: {
|
|
145
|
+
excludeSelf?: boolean;
|
|
146
|
+
}): TData[];
|
|
147
|
+
/**
|
|
148
|
+
* Like {@link listPeers} but maps each peer {@link SockaWebSocketSession}
|
|
149
|
+
* (e.g. when you need more than {@link #data}).
|
|
150
|
+
*/
|
|
151
|
+
listPeersWith<R>(map: (session: SockaWebSocketSession<TContract, TData>) => R, options?: {
|
|
152
|
+
excludeSelf?: boolean;
|
|
153
|
+
}): R[];
|
|
154
|
+
/** Count of sessions in this room (same {@link sessions} map), optionally excluding self. */
|
|
155
|
+
peerCount(options?: {
|
|
156
|
+
excludeSelf?: boolean;
|
|
157
|
+
}): number;
|
|
158
|
+
/** Whether any peer sessions exist (optionally excluding self). */
|
|
159
|
+
hasPeers(options?: {
|
|
160
|
+
excludeSelf?: boolean;
|
|
161
|
+
}): boolean;
|
|
80
162
|
/**
|
|
81
163
|
* Invokes the user {@link typeof SockaWebSocketSessionConfig.handleClose} callback.
|
|
82
164
|
* Server adapters should call this when the WebSocket closes, **before** deleting
|
|
@@ -104,4 +186,4 @@ declare class SockaWebSocketSession<TContract extends SockaContract<SockaContrac
|
|
|
104
186
|
*/
|
|
105
187
|
declare function runSockaSessionOnAttached<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfig<TContract, TData>, session: SockaWebSocketSession<TContract, TData>): void;
|
|
106
188
|
|
|
107
|
-
export {
|
|
189
|
+
export { type SockaStrictWebSocketInit as S, SockaWebSocketSession as a, type SockaWebSocketSessionConfig as b, type SockaWebSocketInit as c, type SockaPushSession as d, broadcastSockaEventToPeers as e, type SockaEmitCapable as f, runSockaSessionOnAttached as r };
|
package/dist/bun/index.d.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import { ServerWebSocket } from 'bun';
|
|
2
|
-
import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat } from '../socka-report-error-
|
|
3
|
-
import { S as
|
|
2
|
+
import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat } from '../socka-report-error-ixTynx4w.js';
|
|
3
|
+
import { S as SockaStrictWebSocketInit, a as SockaWebSocketSession, b as SockaWebSocketSessionConfig } from '../SockaWebSocketSession-Cza7Fti-.js';
|
|
4
4
|
import '@standard-schema/spec';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Reads the upgrade {@link Request} from Bun **`ServerWebSocket.data`** when your
|
|
8
|
+
* **`fetch`** handler stored it there (e.g. **`server.upgrade(req, { data: { roomId, request: req } })`**).
|
|
9
|
+
*
|
|
10
|
+
* Pair with **`strictUpgradeRequest: true`** on {@link SockaWebSocketSessionConfig} so
|
|
11
|
+
* **`createData`** is typed with {@link SockaStrictWebSocketInit} and **`init.request`**
|
|
12
|
+
* is always defined. If **`request`** is missing from **`data`**, this returns **`undefined`**
|
|
13
|
+
* and strict mode will throw when constructing the session — that usually means you forgot
|
|
14
|
+
* to pass **`request`** on upgrade.
|
|
15
|
+
*/
|
|
16
|
+
declare function sockaBunInitFromWsData(ws: ServerWebSocket<unknown>): SockaStrictWebSocketInit | undefined;
|
|
17
|
+
/** `ServerWebSocket.data` shape after {@link sockaBunUpgrade}. */
|
|
18
|
+
type SockaBunUpgradeData<TExtra> = TExtra & {
|
|
19
|
+
request: Request;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Calls {@link Bun.Server.upgrade} with **`request: req`** merged into **`data`**, so
|
|
23
|
+
* {@link sockaBunInitFromWsData} and **`strictUpgradeRequest: true`** always see the HTTP
|
|
24
|
+
* upgrade request (query params, cookies path).
|
|
25
|
+
*/
|
|
26
|
+
declare function sockaBunUpgrade<TExtra extends Record<string, unknown>>(server: {
|
|
27
|
+
upgrade: (req: Request, opts: {
|
|
28
|
+
data: SockaBunUpgradeData<TExtra>;
|
|
29
|
+
}) => boolean;
|
|
30
|
+
}, req: Request, data?: TExtra): boolean;
|
|
6
31
|
type SockaBunResolveScope<TContract extends SockaContract<SockaContractConfig>, TData, TWsData = undefined> = (ws: ServerWebSocket<TWsData>) => {
|
|
7
32
|
sessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
|
|
8
33
|
config: SockaWebSocketSessionConfig<TContract, TData>;
|
|
@@ -35,4 +60,4 @@ declare function createSockaBunWebSocketHandlers<TContract extends SockaContract
|
|
|
35
60
|
resolveScope: SockaBunResolveScope<TContract, TData, TWsData>;
|
|
36
61
|
}): SockaBunWebSocketHandlers<TContract, TData, TWsData>;
|
|
37
62
|
|
|
38
|
-
export { type SockaBunResolveScope, type SockaBunWebSocketHandlers, createSockaBunWebSocketHandlers };
|
|
63
|
+
export { type SockaBunResolveScope, type SockaBunUpgradeData, type SockaBunWebSocketHandlers, createSockaBunWebSocketHandlers, sockaBunInitFromWsData, sockaBunUpgrade };
|
package/dist/bun/index.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
|
|
2
|
-
import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-
|
|
3
|
-
import { reportSockaError } from '../chunk-
|
|
2
|
+
import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-JVLUA3Q5.js';
|
|
3
|
+
import { reportSockaError } from '../chunk-2FNWVCP3.js';
|
|
4
4
|
|
|
5
5
|
// src/bun/index.ts
|
|
6
|
+
function sockaBunInitFromWsData(ws) {
|
|
7
|
+
const d = ws.data;
|
|
8
|
+
if (d && "request" in d && d.request instanceof Request) {
|
|
9
|
+
return { request: d.request };
|
|
10
|
+
}
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
function sockaBunUpgrade(server, req, data) {
|
|
14
|
+
const extra = data ?? {};
|
|
15
|
+
return server.upgrade(req, { data: { ...extra, request: req } });
|
|
16
|
+
}
|
|
6
17
|
function bunHandlersFromResolveScope(resolveScope) {
|
|
7
18
|
const websocket = {
|
|
8
19
|
open(ws) {
|
|
9
20
|
const { sessionMap, config } = resolveScope(ws);
|
|
10
21
|
const domWs = ws;
|
|
11
|
-
const
|
|
22
|
+
const init = sockaBunInitFromWsData(ws);
|
|
23
|
+
const session = new SockaWebSocketSession(
|
|
24
|
+
domWs,
|
|
25
|
+
sessionMap,
|
|
26
|
+
config,
|
|
27
|
+
init
|
|
28
|
+
);
|
|
12
29
|
sessionMap.set(domWs, session);
|
|
13
30
|
runSockaSessionOnAttached(config, session);
|
|
14
31
|
},
|
|
@@ -62,7 +79,13 @@ function bunHandlersFromConfig(config, maybeOptions) {
|
|
|
62
79
|
const websocket = {
|
|
63
80
|
open(ws) {
|
|
64
81
|
const domWs = ws;
|
|
65
|
-
const
|
|
82
|
+
const init = sockaBunInitFromWsData(ws);
|
|
83
|
+
const session = new SockaWebSocketSession(
|
|
84
|
+
domWs,
|
|
85
|
+
sessionMap,
|
|
86
|
+
config,
|
|
87
|
+
init
|
|
88
|
+
);
|
|
66
89
|
sessionMap.set(domWs, session);
|
|
67
90
|
runSockaSessionOnAttached(config, session);
|
|
68
91
|
},
|
|
@@ -116,6 +139,6 @@ function createSockaBunWebSocketHandlers(configOrOptions, maybeOptions) {
|
|
|
116
139
|
);
|
|
117
140
|
}
|
|
118
141
|
|
|
119
|
-
export { createSockaBunWebSocketHandlers };
|
|
142
|
+
export { createSockaBunWebSocketHandlers, sockaBunInitFromWsData, sockaBunUpgrade };
|
|
120
143
|
//# sourceMappingURL=index.js.map
|
|
121
144
|
//# sourceMappingURL=index.js.map
|
package/dist/bun/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bun/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ServerWebSocket } from \"bun\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\n\nexport type SockaBunResolveScope<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData = undefined,\n> = (ws: ServerWebSocket<TWsData>) => {\n\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n};\n\nexport type SockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData = undefined,\n> = {\n\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t/** Pass into `Bun.serve({ ..., websocket })`. */\n\twebsocket: {\n\t\topen: (ws: ServerWebSocket<TWsData>) => void;\n\t\tmessage: (\n\t\t\tws: ServerWebSocket<TWsData>,\n\t\t\tmessage: unknown,\n\t\t) => void | Promise<void>;\n\t\tclose: (ws: ServerWebSocket<TWsData>) => void | Promise<void>;\n\t};\n\twireFormat: SockaWireFormat;\n};\n\nfunction bunHandlersFromResolveScope<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData,\n>(\n\tresolveScope: SockaBunResolveScope<TContract, TData, TWsData>,\n): SockaBunWebSocketHandlers<TContract, TData, TWsData> {\n\tconst websocket: SockaBunWebSocketHandlers<\n\t\tTContract,\n\t\tTData,\n\t\tTWsData\n\t>[\"websocket\"] = {\n\t\topen(ws: ServerWebSocket<TWsData>) {\n\t\t\tconst { sessionMap, config } = resolveScope(ws);\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = new SockaWebSocketSession(domWs, sessionMap, config);\n\t\t\tsessionMap.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(config, session);\n\t\t},\n\t\tasync message(ws: ServerWebSocket<TWsData>, message: unknown) {\n\t\t\tconst { sessionMap, config } = resolveScope(ws);\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\tif (!session) return;\n\t\t\tconst wireFormat = config.wireFormat ?? \"json\";\n\t\t\ttry {\n\t\t\t\tawait dispatchSockaInboundMessage(\n\t\t\t\t\tsession,\n\t\t\t\t\twireFormat,\n\t\t\t\t\tmessage as MessageEvent[\"data\"],\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"bun\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tasync close(ws: ServerWebSocket<TWsData>) {\n\t\t\tconst { sessionMap, config } = resolveScope(ws);\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\ttry {\n\t\t\t\tif (session) {\n\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tsessionMap.delete(domWs);\n\t\t\t}\n\t\t},\n\t};\n\n\treturn {\n\t\tsessionMap: new Map(),\n\t\twebsocket,\n\t\twireFormat: \"json\",\n\t};\n}\n\nfunction bunHandlersFromConfig<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\tmaybeOptions?: {\n\t\tsessionMap?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t},\n): SockaBunWebSocketHandlers<TContract, TData, undefined> {\n\tconst sessionMap =\n\t\tmaybeOptions?.sessionMap ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst wireFormat = config.wireFormat ?? \"json\";\n\n\tconst websocket: SockaBunWebSocketHandlers<\n\t\tTContract,\n\t\tTData,\n\t\tundefined\n\t>[\"websocket\"] = {\n\t\topen(ws: ServerWebSocket<undefined>) {\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = new SockaWebSocketSession(domWs, sessionMap, config);\n\t\t\tsessionMap.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(config, session);\n\t\t},\n\t\tasync message(ws: ServerWebSocket<undefined>, message: unknown) {\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\tif (!session) return;\n\t\t\ttry {\n\t\t\t\tawait dispatchSockaInboundMessage(\n\t\t\t\t\tsession,\n\t\t\t\t\twireFormat,\n\t\t\t\t\tmessage as MessageEvent[\"data\"],\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"bun\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tasync close(ws: ServerWebSocket<undefined>) {\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\ttry {\n\t\t\t\tif (session) {\n\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tsessionMap.delete(domWs);\n\t\t\t}\n\t\t},\n\t};\n\n\treturn { sessionMap, websocket, wireFormat };\n}\n\n/**\n * WebSocket handlers for `Bun.serve` when using `ServerWebSocket` (no\n * `addEventListener`). Inbound frames are dispatched with the same logic as\n * `attachSockaWebSocket`.\n *\n * **Single-room:** pass a {@link SockaWebSocketSessionConfig} and optional shared `sessionMap`.\n *\n * **Multi-room:** pass `{ resolveScope }` where `resolveScope(ws)` returns the\n * `sessionMap` and `config` for that socket’s scope (e.g. from `ws.data.roomId`).\n * The returned `sessionMap` is an empty placeholder; real maps come from `resolveScope`.\n */\nexport function createSockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\toptions?: {\n\t\tsessionMap?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t},\n): SockaBunWebSocketHandlers<TContract, TData, undefined>;\n\nexport function createSockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData,\n>(options: {\n\tresolveScope: SockaBunResolveScope<TContract, TData, TWsData>;\n}): SockaBunWebSocketHandlers<TContract, TData, TWsData>;\n\nexport function createSockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfigOrOptions:\n\t\t| SockaWebSocketSessionConfig<TContract, TData>\n\t\t| { resolveScope: SockaBunResolveScope<TContract, TData, unknown> },\n\tmaybeOptions?: {\n\t\tsessionMap?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t},\n):\n\t| SockaBunWebSocketHandlers<TContract, TData, undefined>\n\t| SockaBunWebSocketHandlers<TContract, TData, unknown> {\n\tconst isResolveScope =\n\t\ttypeof configOrOptions === \"object\" &&\n\t\tconfigOrOptions !== null &&\n\t\t\"resolveScope\" in configOrOptions &&\n\t\ttypeof (configOrOptions as { resolveScope: unknown }).resolveScope ===\n\t\t\t\"function\";\n\n\tif (isResolveScope) {\n\t\treturn bunHandlersFromResolveScope(\n\t\t\t(\n\t\t\t\tconfigOrOptions as {\n\t\t\t\t\tresolveScope: SockaBunResolveScope<TContract, TData, unknown>;\n\t\t\t\t}\n\t\t\t).resolveScope,\n\t\t);\n\t}\n\treturn bunHandlersFromConfig(\n\t\tconfigOrOptions as SockaWebSocketSessionConfig<TContract, TData>,\n\t\tmaybeOptions,\n\t);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/bun/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ServerWebSocket } from \"bun\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaStrictWebSocketInit,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\n\n/**\n * Reads the upgrade {@link Request} from Bun **`ServerWebSocket.data`** when your\n * **`fetch`** handler stored it there (e.g. **`server.upgrade(req, { data: { roomId, request: req } })`**).\n *\n * Pair with **`strictUpgradeRequest: true`** on {@link SockaWebSocketSessionConfig} so\n * **`createData`** is typed with {@link SockaStrictWebSocketInit} and **`init.request`**\n * is always defined. If **`request`** is missing from **`data`**, this returns **`undefined`**\n * and strict mode will throw when constructing the session — that usually means you forgot\n * to pass **`request`** on upgrade.\n */\nexport function sockaBunInitFromWsData(\n\tws: ServerWebSocket<unknown>,\n): SockaStrictWebSocketInit | undefined {\n\tconst d = ws.data as Record<string, unknown> | undefined;\n\tif (d && \"request\" in d && d.request instanceof Request) {\n\t\treturn { request: d.request };\n\t}\n\treturn undefined;\n}\n\n/** `ServerWebSocket.data` shape after {@link sockaBunUpgrade}. */\nexport type SockaBunUpgradeData<TExtra> = TExtra & { request: Request };\n\n/**\n * Calls {@link Bun.Server.upgrade} with **`request: req`** merged into **`data`**, so\n * {@link sockaBunInitFromWsData} and **`strictUpgradeRequest: true`** always see the HTTP\n * upgrade request (query params, cookies path).\n */\nexport function sockaBunUpgrade<TExtra extends Record<string, unknown>>(\n\tserver: {\n\t\tupgrade: (\n\t\t\treq: Request,\n\t\t\topts: { data: SockaBunUpgradeData<TExtra> },\n\t\t) => boolean;\n\t},\n\treq: Request,\n\tdata?: TExtra,\n): boolean {\n\tconst extra = (data ?? ({} as TExtra)) as TExtra;\n\treturn server.upgrade(req, { data: { ...extra, request: req } });\n}\n\nexport type SockaBunResolveScope<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData = undefined,\n> = (ws: ServerWebSocket<TWsData>) => {\n\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n};\n\nexport type SockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData = undefined,\n> = {\n\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t/** Pass into `Bun.serve({ ..., websocket })`. */\n\twebsocket: {\n\t\topen: (ws: ServerWebSocket<TWsData>) => void;\n\t\tmessage: (\n\t\t\tws: ServerWebSocket<TWsData>,\n\t\t\tmessage: unknown,\n\t\t) => void | Promise<void>;\n\t\tclose: (ws: ServerWebSocket<TWsData>) => void | Promise<void>;\n\t};\n\twireFormat: SockaWireFormat;\n};\n\nfunction bunHandlersFromResolveScope<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData,\n>(\n\tresolveScope: SockaBunResolveScope<TContract, TData, TWsData>,\n): SockaBunWebSocketHandlers<TContract, TData, TWsData> {\n\tconst websocket: SockaBunWebSocketHandlers<\n\t\tTContract,\n\t\tTData,\n\t\tTWsData\n\t>[\"websocket\"] = {\n\t\topen(ws: ServerWebSocket<TWsData>) {\n\t\t\tconst { sessionMap, config } = resolveScope(ws);\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst init = sockaBunInitFromWsData(ws);\n\t\t\tconst session = new SockaWebSocketSession(\n\t\t\t\tdomWs,\n\t\t\t\tsessionMap,\n\t\t\t\tconfig,\n\t\t\t\tinit,\n\t\t\t);\n\t\t\tsessionMap.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(config, session);\n\t\t},\n\t\tasync message(ws: ServerWebSocket<TWsData>, message: unknown) {\n\t\t\tconst { sessionMap, config } = resolveScope(ws);\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\tif (!session) return;\n\t\t\tconst wireFormat = config.wireFormat ?? \"json\";\n\t\t\ttry {\n\t\t\t\tawait dispatchSockaInboundMessage(\n\t\t\t\t\tsession,\n\t\t\t\t\twireFormat,\n\t\t\t\t\tmessage as MessageEvent[\"data\"],\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"bun\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tasync close(ws: ServerWebSocket<TWsData>) {\n\t\t\tconst { sessionMap, config } = resolveScope(ws);\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\ttry {\n\t\t\t\tif (session) {\n\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tsessionMap.delete(domWs);\n\t\t\t}\n\t\t},\n\t};\n\n\treturn {\n\t\tsessionMap: new Map(),\n\t\twebsocket,\n\t\twireFormat: \"json\",\n\t};\n}\n\nfunction bunHandlersFromConfig<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\tmaybeOptions?: {\n\t\tsessionMap?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t},\n): SockaBunWebSocketHandlers<TContract, TData, undefined> {\n\tconst sessionMap =\n\t\tmaybeOptions?.sessionMap ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst wireFormat = config.wireFormat ?? \"json\";\n\n\tconst websocket: SockaBunWebSocketHandlers<\n\t\tTContract,\n\t\tTData,\n\t\tundefined\n\t>[\"websocket\"] = {\n\t\topen(ws: ServerWebSocket<undefined>) {\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst init = sockaBunInitFromWsData(ws);\n\t\t\tconst session = new SockaWebSocketSession(\n\t\t\t\tdomWs,\n\t\t\t\tsessionMap,\n\t\t\t\tconfig,\n\t\t\t\tinit,\n\t\t\t);\n\t\t\tsessionMap.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(config, session);\n\t\t},\n\t\tasync message(ws: ServerWebSocket<undefined>, message: unknown) {\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\tif (!session) return;\n\t\t\ttry {\n\t\t\t\tawait dispatchSockaInboundMessage(\n\t\t\t\t\tsession,\n\t\t\t\t\twireFormat,\n\t\t\t\t\tmessage as MessageEvent[\"data\"],\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"bun\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tasync close(ws: ServerWebSocket<undefined>) {\n\t\t\tconst domWs = ws as unknown as WebSocket;\n\t\t\tconst session = sessionMap.get(domWs);\n\t\t\ttry {\n\t\t\t\tif (session) {\n\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tsessionMap.delete(domWs);\n\t\t\t}\n\t\t},\n\t};\n\n\treturn { sessionMap, websocket, wireFormat };\n}\n\n/**\n * WebSocket handlers for `Bun.serve` when using `ServerWebSocket` (no\n * `addEventListener`). Inbound frames are dispatched with the same logic as\n * `attachSockaWebSocket`.\n *\n * **Single-room:** pass a {@link SockaWebSocketSessionConfig} and optional shared `sessionMap`.\n *\n * **Multi-room:** pass `{ resolveScope }` where `resolveScope(ws)` returns the\n * `sessionMap` and `config` for that socket’s scope (e.g. from `ws.data.roomId`).\n * The returned `sessionMap` is an empty placeholder; real maps come from `resolveScope`.\n */\nexport function createSockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\toptions?: {\n\t\tsessionMap?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t},\n): SockaBunWebSocketHandlers<TContract, TData, undefined>;\n\nexport function createSockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTWsData,\n>(options: {\n\tresolveScope: SockaBunResolveScope<TContract, TData, TWsData>;\n}): SockaBunWebSocketHandlers<TContract, TData, TWsData>;\n\nexport function createSockaBunWebSocketHandlers<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfigOrOptions:\n\t\t| SockaWebSocketSessionConfig<TContract, TData>\n\t\t| { resolveScope: SockaBunResolveScope<TContract, TData, unknown> },\n\tmaybeOptions?: {\n\t\tsessionMap?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t},\n):\n\t| SockaBunWebSocketHandlers<TContract, TData, undefined>\n\t| SockaBunWebSocketHandlers<TContract, TData, unknown> {\n\tconst isResolveScope =\n\t\ttypeof configOrOptions === \"object\" &&\n\t\tconfigOrOptions !== null &&\n\t\t\"resolveScope\" in configOrOptions &&\n\t\ttypeof (configOrOptions as { resolveScope: unknown }).resolveScope ===\n\t\t\t\"function\";\n\n\tif (isResolveScope) {\n\t\treturn bunHandlersFromResolveScope(\n\t\t\t(\n\t\t\t\tconfigOrOptions as {\n\t\t\t\t\tresolveScope: SockaBunResolveScope<TContract, TData, unknown>;\n\t\t\t\t}\n\t\t\t).resolveScope,\n\t\t);\n\t}\n\treturn bunHandlersFromConfig(\n\t\tconfigOrOptions as SockaWebSocketSessionConfig<TContract, TData>,\n\t\tmaybeOptions,\n\t);\n}\n"]}
|