@kyneta/websocket-transport 1.3.0 → 1.4.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 +28 -15
- package/dist/browser.d.ts +59 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +2 -0
- package/dist/bun.d.ts +22 -22
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +95 -43
- package/dist/bun.js.map +1 -1
- package/dist/client-transport-CKjXedwS.d.ts +134 -0
- package/dist/client-transport-CKjXedwS.d.ts.map +1 -0
- package/dist/client-transport-DIZ-LJxs.js +510 -0
- package/dist/client-transport-DIZ-LJxs.js.map +1 -0
- package/dist/server.d.ts +154 -115
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +298 -296
- package/dist/server.js.map +1 -1
- package/dist/types-CJAcr1Df.d.ts +199 -0
- package/dist/types-CJAcr1Df.d.ts.map +1 -0
- package/package.json +15 -15
- package/src/__tests__/client-transport.test.ts +167 -0
- package/src/{client.ts → browser.ts} +15 -10
- package/src/bun-websocket.ts +5 -5
- package/src/bun.ts +1 -1
- package/src/client-transport.ts +42 -67
- package/src/connection.ts +1 -1
- package/src/server-transport.ts +10 -10
- package/src/server.ts +22 -4
- package/src/service-client.ts +52 -0
- package/src/types.ts +74 -6
- package/dist/chunk-PSG3LLT5.js +0 -111
- package/dist/chunk-PSG3LLT5.js.map +0 -1
- package/dist/client.d.ts +0 -207
- package/dist/client.js +0 -508
- package/dist/client.js.map +0 -1
- package/dist/types-DdNb8cAz.d.ts +0 -149
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# @kyneta/websocket-
|
|
1
|
+
# @kyneta/websocket-transport
|
|
2
2
|
|
|
3
|
-
Websocket
|
|
3
|
+
Websocket transport for `@kyneta/exchange` — browser, server, and Bun integration. Provides bidirectional real-time sync over Websockets using the `@kyneta/wire` binary protocol (CBOR codec + framing + fragmentation).
|
|
4
4
|
|
|
5
5
|
## Subpath Exports
|
|
6
6
|
|
|
7
7
|
| Export | Entry point | Environment |
|
|
8
8
|
|--------|-------------|-------------|
|
|
9
|
-
| `@kyneta/websocket-
|
|
10
|
-
| `@kyneta/websocket-
|
|
11
|
-
| `@kyneta/websocket-
|
|
9
|
+
| `@kyneta/websocket-transport/browser` | `./dist/browser.js` | Browser, Bun, Node.js |
|
|
10
|
+
| `@kyneta/websocket-transport/server` | `./dist/server.js` | Bun, Node.js |
|
|
11
|
+
| `@kyneta/websocket-transport/bun` | `./dist/bun.js` | Bun only |
|
|
12
12
|
|
|
13
13
|
## Server Setup
|
|
14
14
|
|
|
@@ -18,8 +18,8 @@ Use `createBunWebsocketHandlers` for zero-boilerplate integration with `Bun.serv
|
|
|
18
18
|
|
|
19
19
|
```/dev/null/bun-server.ts#L1-18
|
|
20
20
|
import { Exchange } from "@kyneta/exchange"
|
|
21
|
-
import { WebsocketServerAdapter } from "@kyneta/websocket-
|
|
22
|
-
import { createBunWebsocketHandlers, type BunWebsocketData } from "@kyneta/websocket-
|
|
21
|
+
import { WebsocketServerAdapter } from "@kyneta/websocket-transport/server"
|
|
22
|
+
import { createBunWebsocketHandlers, type BunWebsocketData } from "@kyneta/websocket-transport/bun"
|
|
23
23
|
|
|
24
24
|
const serverAdapter = new WebsocketServerAdapter()
|
|
25
25
|
|
|
@@ -40,7 +40,7 @@ Bun.serve<BunWebsocketData>({
|
|
|
40
40
|
For more control, use `wrapBunWebsocket` directly:
|
|
41
41
|
|
|
42
42
|
```/dev/null/bun-server-manual.ts#L1-17
|
|
43
|
-
import { wrapBunWebsocket, type BunWebsocketData } from "@kyneta/websocket-
|
|
43
|
+
import { wrapBunWebsocket, type BunWebsocketData } from "@kyneta/websocket-transport/bun"
|
|
44
44
|
|
|
45
45
|
Bun.serve<BunWebsocketData>({
|
|
46
46
|
fetch(req, server) {
|
|
@@ -69,7 +69,7 @@ Use `wrapNodeWebsocket` to adapt the `ws` library's `WebSocket` to the framework
|
|
|
69
69
|
|
|
70
70
|
```/dev/null/node-server.ts#L1-16
|
|
71
71
|
import { WebSocketServer } from "ws"
|
|
72
|
-
import { WebsocketServerAdapter, wrapNodeWebsocket } from "@kyneta/websocket-
|
|
72
|
+
import { WebsocketServerAdapter, wrapNodeWebsocket } from "@kyneta/websocket-transport/server"
|
|
73
73
|
|
|
74
74
|
const serverAdapter = new WebsocketServerAdapter()
|
|
75
75
|
|
|
@@ -92,12 +92,13 @@ wss.on("connection", (ws) => {
|
|
|
92
92
|
|
|
93
93
|
Use `createWebsocketClient` for browser-to-server connections:
|
|
94
94
|
|
|
95
|
-
```/dev/null/browser-client.ts#L1-
|
|
95
|
+
```/dev/null/browser-client.ts#L1-13
|
|
96
96
|
import { Exchange } from "@kyneta/exchange"
|
|
97
|
-
import { createWebsocketClient } from "@kyneta/websocket-
|
|
97
|
+
import { createWebsocketClient } from "@kyneta/websocket-transport/browser"
|
|
98
98
|
|
|
99
99
|
const adapter = createWebsocketClient({
|
|
100
100
|
url: "ws://localhost:3000/ws",
|
|
101
|
+
WebSocket,
|
|
101
102
|
reconnect: { enabled: true },
|
|
102
103
|
})
|
|
103
104
|
|
|
@@ -112,7 +113,7 @@ const exchange = new Exchange({
|
|
|
112
113
|
Use `createServiceWebsocketClient` for backend connections that need authentication headers during the Websocket upgrade. Headers are a Bun/Node-specific extension — the browser `WebSocket` API does not support custom headers.
|
|
113
114
|
|
|
114
115
|
```/dev/null/service-client.ts#L1-13
|
|
115
|
-
import { createServiceWebsocketClient } from "@kyneta/websocket-
|
|
116
|
+
import { createServiceWebsocketClient } from "@kyneta/websocket-transport/server"
|
|
116
117
|
|
|
117
118
|
const adapter = createServiceWebsocketClient({
|
|
118
119
|
url: "ws://primary-server:3000/ws",
|
|
@@ -180,7 +181,7 @@ The server may send `"ready"` before the client's `open` event fires (server-rea
|
|
|
180
181
|
The public observation API is powered by `createObservableProgram` from `@kyneta/machine`:
|
|
181
182
|
|
|
182
183
|
```/dev/null/observe-state.ts#L1-18
|
|
183
|
-
import { createWebsocketClient } from "@kyneta/websocket-
|
|
184
|
+
import { createWebsocketClient } from "@kyneta/websocket-transport/browser"
|
|
184
185
|
|
|
185
186
|
const adapter = createWebsocketClient({
|
|
186
187
|
url: "ws://localhost:3000/ws",
|
|
@@ -207,7 +208,7 @@ await adapter.waitForStatus("ready", { timeoutMs: 5000 })
|
|
|
207
208
|
|
|
208
209
|
| Wrapper | Input | Export |
|
|
209
210
|
|---------|-------|--------|
|
|
210
|
-
| `wrapStandardWebsocket(ws)` | Browser `WebSocket` | `./
|
|
211
|
+
| `wrapStandardWebsocket(ws)` | Browser `WebSocket` | `./server` |
|
|
211
212
|
| `wrapNodeWebsocket(ws)` | Node.js `ws` library | `./server` |
|
|
212
213
|
| `wrapBunWebsocket(ws)` | Bun `ServerWebSocket` | `./bun` |
|
|
213
214
|
|
|
@@ -229,7 +230,7 @@ interface Socket {
|
|
|
229
230
|
| Option | Default | Description |
|
|
230
231
|
|--------|---------|-------------|
|
|
231
232
|
| `url` | — | Websocket URL. String or `(peerId) => string` function. |
|
|
232
|
-
| `WebSocket` |
|
|
233
|
+
| `WebSocket` | — | WebSocket constructor (**required**). In browsers, pass `WebSocket`. In Node.js, pass `ws`'s `WebSocket`. |
|
|
233
234
|
| `reconnect.enabled` | `true` | Enable automatic reconnection. |
|
|
234
235
|
| `reconnect.maxAttempts` | `10` | Maximum reconnection attempts before giving up. |
|
|
235
236
|
| `reconnect.baseDelay` | `1000` | Base delay in ms for exponential backoff. |
|
|
@@ -256,6 +257,18 @@ interface Socket {
|
|
|
256
257
|
|
|
257
258
|
The client sends text `"ping"` frames at the configured interval. The server responds with text `"pong"`. This keeps connections alive through proxies and load balancers that terminate idle connections.
|
|
258
259
|
|
|
260
|
+
## Runtime Agnosticism
|
|
261
|
+
|
|
262
|
+
Every entry point (`./browser`, `./server`, `./bun`) is safe to import in any JavaScript runtime — no top-level side effects, no `globalThis` probes. The transport package has zero runtime dependencies; callers provide all implementations:
|
|
263
|
+
|
|
264
|
+
| Entry Point | Purpose | Caller Provides |
|
|
265
|
+
|-------------|---------|----------------|
|
|
266
|
+
| `./browser` | Browser-to-server connections | `WebSocket` constructor (the global `WebSocket` in browsers) |
|
|
267
|
+
| `./server` | Server transport + service-to-service client | `Socket` wrapper via `wrapNodeWebsocket()` or `wrapStandardWebsocket()` |
|
|
268
|
+
| `./bun` | Bun-optimized server handlers | `ServerWebSocket` from Bun's callback API |
|
|
269
|
+
|
|
270
|
+
This follows the same structural-typing principle used throughout: `wrapNodeWebsocket` takes a `NodeWebsocketLike` — it never imports `ws`. `wrapBunWebsocket` takes a Bun `ServerWebSocket` — it never imports Bun APIs at the top level.
|
|
271
|
+
|
|
259
272
|
## Peer Dependencies
|
|
260
273
|
|
|
261
274
|
```/dev/null/package.json#L1-4
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { a as SocketReadyState, c as WebSocketConstructor, d as WebsocketClientState, f as WebsocketClientStateTransition, i as Socket, l as WebSocketLike, o as TransitionListener, r as READY_STATE, s as WebSocketCloseEvent, t as DisconnectReason, u as WebSocketMessageEvent } from "./types-CJAcr1Df.js";
|
|
2
|
+
import { a as createWebsocketClient, i as WebsocketClientTransport, n as WebsocketClientLifecycleEvents, r as WebsocketClientOptions, t as DEFAULT_FRAGMENT_THRESHOLD } from "./client-transport-CKjXedwS.js";
|
|
3
|
+
import { ReconnectOptions } from "@kyneta/transport";
|
|
4
|
+
import { Program } from "@kyneta/machine";
|
|
5
|
+
|
|
6
|
+
//#region src/client-program.d.ts
|
|
7
|
+
type WsClientMsg = {
|
|
8
|
+
type: "start";
|
|
9
|
+
} | {
|
|
10
|
+
type: "socket-opened";
|
|
11
|
+
} | {
|
|
12
|
+
type: "server-ready";
|
|
13
|
+
} | {
|
|
14
|
+
type: "socket-closed";
|
|
15
|
+
code: number;
|
|
16
|
+
reason: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "socket-error";
|
|
19
|
+
error: Error;
|
|
20
|
+
} | {
|
|
21
|
+
type: "reconnect-timer-fired";
|
|
22
|
+
} | {
|
|
23
|
+
type: "stop";
|
|
24
|
+
};
|
|
25
|
+
type WsClientEffect = {
|
|
26
|
+
type: "create-websocket";
|
|
27
|
+
attempt: number;
|
|
28
|
+
} | {
|
|
29
|
+
type: "close-websocket";
|
|
30
|
+
} | {
|
|
31
|
+
type: "add-channel-and-establish";
|
|
32
|
+
} | {
|
|
33
|
+
type: "remove-channel";
|
|
34
|
+
} | {
|
|
35
|
+
type: "start-reconnect-timer";
|
|
36
|
+
delayMs: number;
|
|
37
|
+
} | {
|
|
38
|
+
type: "cancel-reconnect-timer";
|
|
39
|
+
} | {
|
|
40
|
+
type: "start-keepalive";
|
|
41
|
+
} | {
|
|
42
|
+
type: "stop-keepalive";
|
|
43
|
+
};
|
|
44
|
+
interface WsClientProgramOptions {
|
|
45
|
+
reconnect?: Partial<ReconnectOptions>;
|
|
46
|
+
/** Inject jitter source for deterministic testing. Default: () => Math.random() * 1000 */
|
|
47
|
+
jitterFn?: () => number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create the websocket client connection lifecycle program — a pure Mealy machine.
|
|
51
|
+
*
|
|
52
|
+
* The returned `Program<WsClientMsg, WebsocketClientState, WsClientEffect>`
|
|
53
|
+
* encodes every state transition and effect as inspectable data. The imperative
|
|
54
|
+
* shell interprets `WsClientEffect` as actual I/O.
|
|
55
|
+
*/
|
|
56
|
+
declare function createWsClientProgram(options?: WsClientProgramOptions): Program<WsClientMsg, WebsocketClientState, WsClientEffect>;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { DEFAULT_FRAGMENT_THRESHOLD, type DisconnectReason, READY_STATE, type Socket, type SocketReadyState, type TransitionListener, type WebSocketCloseEvent, type WebSocketConstructor, type WebSocketLike, type WebSocketMessageEvent, type WebsocketClientLifecycleEvents, type WebsocketClientOptions, type WebsocketClientState, type WebsocketClientStateTransition, WebsocketClientTransport, type WsClientEffect, type WsClientMsg, type WsClientProgramOptions, createWebsocketClient, createWsClientProgram };
|
|
59
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","names":[],"sources":["../src/client-program.ts"],"mappings":";;;;;;KA4BY,WAAA;EACN,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;EAAuB,IAAA;EAAc,MAAA;AAAA;EACrC,IAAA;EAAsB,KAAA,EAAO,KAAA;AAAA;EAC7B,IAAA;AAAA;EACA,IAAA;AAAA;AAAA,KAMM,cAAA;EACN,IAAA;EAA0B,OAAA;AAAA;EAC1B,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;EAA+B,OAAA;AAAA;EAC/B,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;AAAA,UAMW,sBAAA;EACf,SAAA,GAAY,OAAA,CAAQ,gBAAA;EAAD;EAEnB,QAAA;AAAA;;;;;AAUF;;;iBAAgB,qBAAA,CACd,OAAA,GAAS,sBAAA,GACR,OAAA,CAAQ,WAAA,EAAa,oBAAA,EAAsB,cAAA"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { i as READY_STATE, n as WebsocketClientTransport, r as createWebsocketClient, s as createWsClientProgram, t as DEFAULT_FRAGMENT_THRESHOLD } from "./client-transport-DIZ-LJxs.js";
|
|
2
|
+
export { DEFAULT_FRAGMENT_THRESHOLD, READY_STATE, WebsocketClientTransport, createWebsocketClient, createWsClientProgram };
|
package/dist/bun.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export { D as DisconnectReason, a as SocketReadyState } from './types-DdNb8cAz.js';
|
|
4
|
-
import '@kyneta/transport';
|
|
1
|
+
import { a as SocketReadyState, i as Socket, t as DisconnectReason } from "./types-CJAcr1Df.js";
|
|
2
|
+
import { ServerWebSocket } from "bun";
|
|
5
3
|
|
|
4
|
+
//#region src/bun-websocket.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Data structure stored in `ws.data` for handler callbacks.
|
|
8
7
|
* Use this type when defining your `Bun.serve()` generic.
|
|
@@ -15,10 +14,10 @@ import '@kyneta/transport';
|
|
|
15
14
|
* ```
|
|
16
15
|
*/
|
|
17
16
|
type BunWebsocketData = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
handlers: {
|
|
18
|
+
onMessage?: (data: Uint8Array | string) => void;
|
|
19
|
+
onClose?: (code: number, reason: string) => void;
|
|
20
|
+
};
|
|
22
21
|
};
|
|
23
22
|
/**
|
|
24
23
|
* Wrap Bun's `ServerWebSocket` to match the `Socket` interface.
|
|
@@ -30,8 +29,8 @@ type BunWebsocketData = {
|
|
|
30
29
|
*
|
|
31
30
|
* @example
|
|
32
31
|
* ```typescript
|
|
33
|
-
* import { WebsocketServerTransport } from "@kyneta/websocket-
|
|
34
|
-
* import { wrapBunWebsocket, type BunWebsocketData } from "@kyneta/websocket-
|
|
32
|
+
* import { WebsocketServerTransport } from "@kyneta/websocket-transport/server"
|
|
33
|
+
* import { wrapBunWebsocket, type BunWebsocketData } from "@kyneta/websocket-transport/bun"
|
|
35
34
|
*
|
|
36
35
|
* const serverAdapter = new WebsocketServerTransport()
|
|
37
36
|
*
|
|
@@ -62,8 +61,8 @@ declare function wrapBunWebsocket(ws: ServerWebSocket<BunWebsocketData>): Socket
|
|
|
62
61
|
*
|
|
63
62
|
* @example
|
|
64
63
|
* ```typescript
|
|
65
|
-
* import { WebsocketServerTransport } from "@kyneta/websocket-
|
|
66
|
-
* import { createBunWebsocketHandlers, type BunWebsocketData } from "@kyneta/websocket-
|
|
64
|
+
* import { WebsocketServerTransport } from "@kyneta/websocket-transport/server"
|
|
65
|
+
* import { createBunWebsocketHandlers, type BunWebsocketData } from "@kyneta/websocket-transport/bun"
|
|
67
66
|
*
|
|
68
67
|
* const serverAdapter = new WebsocketServerTransport()
|
|
69
68
|
*
|
|
@@ -77,15 +76,16 @@ declare function wrapBunWebsocket(ws: ServerWebSocket<BunWebsocketData>): Socket
|
|
|
77
76
|
* ```
|
|
78
77
|
*/
|
|
79
78
|
declare function createBunWebsocketHandlers(wsAdapter: {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
handleConnection: (opts: {
|
|
80
|
+
socket: Socket;
|
|
81
|
+
}) => {
|
|
82
|
+
start: () => void;
|
|
83
|
+
};
|
|
85
84
|
}): {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
open(ws: ServerWebSocket<BunWebsocketData>): void;
|
|
86
|
+
message(ws: ServerWebSocket<BunWebsocketData>, msg: string | ArrayBuffer | Buffer): void;
|
|
87
|
+
close(ws: ServerWebSocket<BunWebsocketData>, code: number, reason: string): void;
|
|
89
88
|
};
|
|
90
|
-
|
|
91
|
-
export { type BunWebsocketData, Socket, createBunWebsocketHandlers, wrapBunWebsocket };
|
|
89
|
+
//#endregion
|
|
90
|
+
export { type BunWebsocketData, type DisconnectReason, type Socket, type SocketReadyState, createBunWebsocketHandlers, wrapBunWebsocket };
|
|
91
|
+
//# sourceMappingURL=bun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","names":[],"sources":["../src/bun-websocket.ts"],"mappings":";;;;;;AA+BA;;;;;;;;;KAAY,gBAAA;EACV,QAAA;IACE,SAAA,IAAa,IAAA,EAAM,UAAA;IACnB,OAAA,IAAW,IAAA,UAAc,MAAA;EAAA;AAAA;;;;;;;;;;;;;AAyG7B;;;;;;;;;;;;;;;;;;;;iBAjEgB,gBAAA,CACd,EAAA,EAAI,eAAA,CAAgB,gBAAA,IACnB,MAAA;;;;;;;;;;;;;;;;;;;;;;;;iBA+Da,0BAAA,CAA2B,SAAA;EACzC,gBAAA,GAAmB,IAAA;IAAQ,MAAA,EAAQ,MAAA;EAAA;IAAe,KAAA;EAAA;AAAA;WAGvC,eAAA,CAAgB,gBAAA;cAInB,eAAA,CAAgB,gBAAA,GAAiB,GAAA,WACvB,WAAA,GAAc,MAAA;YAUpB,eAAA,CAAgB,gBAAA,GAAiB,IAAA,UAAc,MAAA;AAAA"}
|
package/dist/bun.js
CHANGED
|
@@ -1,48 +1,100 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/bun-websocket.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wrap Bun's `ServerWebSocket` to match the `Socket` interface.
|
|
4
|
+
*
|
|
5
|
+
* Bun's WebSocket API uses server-level callbacks (`websocket: { message, close }`)
|
|
6
|
+
* rather than per-socket event handlers. This wrapper bridges that gap by
|
|
7
|
+
* storing handlers in `ws.data` and having the server-level callbacks delegate
|
|
8
|
+
* to them.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { WebsocketServerTransport } from "@kyneta/websocket-transport/server"
|
|
13
|
+
* import { wrapBunWebsocket, type BunWebsocketData } from "@kyneta/websocket-transport/bun"
|
|
14
|
+
*
|
|
15
|
+
* const serverAdapter = new WebsocketServerTransport()
|
|
16
|
+
*
|
|
17
|
+
* Bun.serve<BunWebsocketData>({
|
|
18
|
+
* websocket: {
|
|
19
|
+
* open(ws) {
|
|
20
|
+
* const socket = wrapBunWebsocket(ws)
|
|
21
|
+
* serverAdapter.handleConnection({ socket }).start()
|
|
22
|
+
* },
|
|
23
|
+
* message(ws, msg) {
|
|
24
|
+
* const data = msg instanceof ArrayBuffer ? new Uint8Array(msg) : msg
|
|
25
|
+
* ws.data?.handlers?.onMessage?.(data)
|
|
26
|
+
* },
|
|
27
|
+
* close(ws, code, reason) {
|
|
28
|
+
* ws.data?.handlers?.onClose?.(code, reason)
|
|
29
|
+
* },
|
|
30
|
+
* },
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
2
34
|
function wrapBunWebsocket(ws) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
};
|
|
35
|
+
ws.data = { handlers: {} };
|
|
36
|
+
return {
|
|
37
|
+
send(data) {
|
|
38
|
+
ws.send(data);
|
|
39
|
+
},
|
|
40
|
+
close(code, reason) {
|
|
41
|
+
ws.close(code, reason);
|
|
42
|
+
},
|
|
43
|
+
onMessage(handler) {
|
|
44
|
+
ws.data.handlers.onMessage = handler;
|
|
45
|
+
},
|
|
46
|
+
onClose(handler) {
|
|
47
|
+
ws.data.handlers.onClose = handler;
|
|
48
|
+
},
|
|
49
|
+
onError(_handler) {},
|
|
50
|
+
get readyState() {
|
|
51
|
+
return [
|
|
52
|
+
"connecting",
|
|
53
|
+
"open",
|
|
54
|
+
"closing",
|
|
55
|
+
"closed"
|
|
56
|
+
][ws.readyState] ?? "closed";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
29
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Create Bun Websocket handlers that integrate with `WebsocketServerTransport`.
|
|
62
|
+
*
|
|
63
|
+
* This helper eliminates boilerplate by providing pre-configured handlers
|
|
64
|
+
* for `open`, `message`, and `close` events that automatically wire up
|
|
65
|
+
* to the adapter's `handleConnection()` method.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* import { WebsocketServerTransport } from "@kyneta/websocket-transport/server"
|
|
70
|
+
* import { createBunWebsocketHandlers, type BunWebsocketData } from "@kyneta/websocket-transport/bun"
|
|
71
|
+
*
|
|
72
|
+
* const serverAdapter = new WebsocketServerTransport()
|
|
73
|
+
*
|
|
74
|
+
* Bun.serve<BunWebsocketData>({
|
|
75
|
+
* fetch(req, server) {
|
|
76
|
+
* server.upgrade(req)
|
|
77
|
+
* return new Response("upgrade failed", { status: 400 })
|
|
78
|
+
* },
|
|
79
|
+
* websocket: createBunWebsocketHandlers(serverAdapter),
|
|
80
|
+
* })
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
30
83
|
function createBunWebsocketHandlers(wsAdapter) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
84
|
+
return {
|
|
85
|
+
open(ws) {
|
|
86
|
+
wsAdapter.handleConnection({ socket: wrapBunWebsocket(ws) }).start();
|
|
87
|
+
},
|
|
88
|
+
message(ws, msg) {
|
|
89
|
+
const data = msg instanceof ArrayBuffer ? new Uint8Array(msg) : Buffer.isBuffer(msg) ? new Uint8Array(msg) : msg;
|
|
90
|
+
ws.data.handlers.onMessage?.(data);
|
|
91
|
+
},
|
|
92
|
+
close(ws, code, reason) {
|
|
93
|
+
ws.data.handlers.onClose?.(code, reason);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
43
96
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { createBunWebsocketHandlers, wrapBunWebsocket };
|
|
99
|
+
|
|
48
100
|
//# sourceMappingURL=bun.js.map
|
package/dist/bun.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bun-websocket.ts"],"sourcesContent":["// bun-websocket — Bun-specific Websocket wrapper for @kyneta/websocket-
|
|
1
|
+
{"version":3,"file":"bun.js","names":[],"sources":["../src/bun-websocket.ts"],"sourcesContent":["// bun-websocket — Bun-specific Websocket wrapper for @kyneta/websocket-transport.\n//\n// Provides a wrapper to adapt Bun's ServerWebSocket to the Socket interface\n// expected by WebsocketServerTransport.\n//\n// Bun's WebSocket API is callback-based at the server level (not per-socket),\n// so we bridge that gap by storing handlers in ws.data.\n//\n// Ported from @loro-extended/adapter-websocket's bun.ts with kyneta\n// naming conventions applied.\n\n/// <reference types=\"bun-types\" />\n\nimport type { ServerWebSocket } from \"bun\"\nimport type { Socket, SocketReadyState } from \"./types.js\"\n\n// ---------------------------------------------------------------------------\n// BunWebsocketData — stored in ws.data for per-socket handler callbacks\n// ---------------------------------------------------------------------------\n\n/**\n * Data structure stored in `ws.data` for handler callbacks.\n * Use this type when defining your `Bun.serve()` generic.\n *\n * @example\n * ```typescript\n * Bun.serve<BunWebsocketData>({\n * websocket: { ... }\n * })\n * ```\n */\nexport type BunWebsocketData = {\n handlers: {\n onMessage?: (data: Uint8Array | string) => void\n onClose?: (code: number, reason: string) => void\n }\n}\n\n// ---------------------------------------------------------------------------\n// wrapBunWebsocket\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap Bun's `ServerWebSocket` to match the `Socket` interface.\n *\n * Bun's WebSocket API uses server-level callbacks (`websocket: { message, close }`)\n * rather than per-socket event handlers. This wrapper bridges that gap by\n * storing handlers in `ws.data` and having the server-level callbacks delegate\n * to them.\n *\n * @example\n * ```typescript\n * import { WebsocketServerTransport } from \"@kyneta/websocket-transport/server\"\n * import { wrapBunWebsocket, type BunWebsocketData } from \"@kyneta/websocket-transport/bun\"\n *\n * const serverAdapter = new WebsocketServerTransport()\n *\n * Bun.serve<BunWebsocketData>({\n * websocket: {\n * open(ws) {\n * const socket = wrapBunWebsocket(ws)\n * serverAdapter.handleConnection({ socket }).start()\n * },\n * message(ws, msg) {\n * const data = msg instanceof ArrayBuffer ? new Uint8Array(msg) : msg\n * ws.data?.handlers?.onMessage?.(data)\n * },\n * close(ws, code, reason) {\n * ws.data?.handlers?.onClose?.(code, reason)\n * },\n * },\n * })\n * ```\n */\nexport function wrapBunWebsocket(\n ws: ServerWebSocket<BunWebsocketData>,\n): Socket {\n ws.data = { handlers: {} }\n\n return {\n send(data: Uint8Array | string): void {\n ws.send(data)\n },\n\n close(code?: number, reason?: string): void {\n ws.close(code, reason)\n },\n\n onMessage(handler: (data: Uint8Array | string) => void): void {\n ws.data.handlers.onMessage = handler\n },\n\n onClose(handler: (code: number, reason: string) => void): void {\n ws.data.handlers.onClose = handler\n },\n\n onError(_handler: (error: Error) => void): void {\n // Bun handles errors at the server level, not per-socket\n },\n\n get readyState(): SocketReadyState {\n const states: SocketReadyState[] = [\n \"connecting\",\n \"open\",\n \"closing\",\n \"closed\",\n ]\n return states[ws.readyState] ?? \"closed\"\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// createBunWebsocketHandlers\n// ---------------------------------------------------------------------------\n\n/**\n * Create Bun Websocket handlers that integrate with `WebsocketServerTransport`.\n *\n * This helper eliminates boilerplate by providing pre-configured handlers\n * for `open`, `message`, and `close` events that automatically wire up\n * to the adapter's `handleConnection()` method.\n *\n * @example\n * ```typescript\n * import { WebsocketServerTransport } from \"@kyneta/websocket-transport/server\"\n * import { createBunWebsocketHandlers, type BunWebsocketData } from \"@kyneta/websocket-transport/bun\"\n *\n * const serverAdapter = new WebsocketServerTransport()\n *\n * Bun.serve<BunWebsocketData>({\n * fetch(req, server) {\n * server.upgrade(req)\n * return new Response(\"upgrade failed\", { status: 400 })\n * },\n * websocket: createBunWebsocketHandlers(serverAdapter),\n * })\n * ```\n */\nexport function createBunWebsocketHandlers(wsAdapter: {\n handleConnection: (opts: { socket: Socket }) => { start: () => void }\n}) {\n return {\n open(ws: ServerWebSocket<BunWebsocketData>) {\n wsAdapter.handleConnection({ socket: wrapBunWebsocket(ws) }).start()\n },\n message(\n ws: ServerWebSocket<BunWebsocketData>,\n msg: string | ArrayBuffer | Buffer,\n ) {\n const data =\n msg instanceof ArrayBuffer\n ? new Uint8Array(msg)\n : Buffer.isBuffer(msg)\n ? new Uint8Array(msg)\n : msg\n ws.data.handlers.onMessage?.(data)\n },\n close(ws: ServerWebSocket<BunWebsocketData>, code: number, reason: string) {\n ws.data.handlers.onClose?.(code, reason)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,iBACd,IACQ;AACR,IAAG,OAAO,EAAE,UAAU,EAAE,EAAE;AAE1B,QAAO;EACL,KAAK,MAAiC;AACpC,MAAG,KAAK,KAAK;;EAGf,MAAM,MAAe,QAAuB;AAC1C,MAAG,MAAM,MAAM,OAAO;;EAGxB,UAAU,SAAoD;AAC5D,MAAG,KAAK,SAAS,YAAY;;EAG/B,QAAQ,SAAuD;AAC7D,MAAG,KAAK,SAAS,UAAU;;EAG7B,QAAQ,UAAwC;EAIhD,IAAI,aAA+B;AAOjC,UANmC;IACjC;IACA;IACA;IACA;IACD,CACa,GAAG,eAAe;;EAEnC;;;;;;;;;;;;;;;;;;;;;;;;;AA8BH,SAAgB,2BAA2B,WAExC;AACD,QAAO;EACL,KAAK,IAAuC;AAC1C,aAAU,iBAAiB,EAAE,QAAQ,iBAAiB,GAAG,EAAE,CAAC,CAAC,OAAO;;EAEtE,QACE,IACA,KACA;GACA,MAAM,OACJ,eAAe,cACX,IAAI,WAAW,IAAI,GACnB,OAAO,SAAS,IAAI,GAClB,IAAI,WAAW,IAAI,GACnB;AACR,MAAG,KAAK,SAAS,YAAY,KAAK;;EAEpC,MAAM,IAAuC,MAAc,QAAgB;AACzE,MAAG,KAAK,SAAS,UAAU,MAAM,OAAO;;EAE3C"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { c as WebSocketConstructor, d as WebsocketClientState, f as WebsocketClientStateTransition, t as DisconnectReason } from "./types-CJAcr1Df.js";
|
|
2
|
+
import { GeneratedChannel, PeerId, Transport, TransportFactory } from "@kyneta/transport";
|
|
3
|
+
import { TransitionListener as TransitionListener$1 } from "@kyneta/machine";
|
|
4
|
+
|
|
5
|
+
//#region src/client-transport.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Default fragment threshold in bytes.
|
|
8
|
+
* AWS API Gateway has a 128KB limit, so 100KB provides a safe margin.
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_FRAGMENT_THRESHOLD: number;
|
|
11
|
+
/**
|
|
12
|
+
* Options for the Websocket client transport (browser connections).
|
|
13
|
+
*/
|
|
14
|
+
interface WebsocketClientOptions {
|
|
15
|
+
/** Websocket URL to connect to. Can be a string or a function of peerId. */
|
|
16
|
+
url: string | ((peerId: PeerId) => string);
|
|
17
|
+
/**
|
|
18
|
+
* WebSocket constructor — caller must provide explicitly.
|
|
19
|
+
*
|
|
20
|
+
* In browsers, pass the global `WebSocket`. In Node.js, pass `ws`'s
|
|
21
|
+
* `WebSocket`. In Bun, pass `globalThis.WebSocket`. The transport
|
|
22
|
+
* never probes `globalThis` on its own.
|
|
23
|
+
*/
|
|
24
|
+
WebSocket: WebSocketConstructor;
|
|
25
|
+
/**
|
|
26
|
+
* Headers to send during Websocket upgrade.
|
|
27
|
+
* Used for authentication in service-to-service communication.
|
|
28
|
+
*
|
|
29
|
+
* Note: Headers are a Bun/Node-specific extension. The browser WebSocket
|
|
30
|
+
* API does not support custom headers per the WHATWG spec.
|
|
31
|
+
*/
|
|
32
|
+
headers?: Record<string, string>;
|
|
33
|
+
/** Reconnection options. */
|
|
34
|
+
reconnect?: {
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
maxAttempts?: number;
|
|
37
|
+
baseDelay?: number;
|
|
38
|
+
maxDelay?: number;
|
|
39
|
+
};
|
|
40
|
+
/** Keepalive interval in ms (default: 30000). */
|
|
41
|
+
keepaliveInterval?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Fragment threshold in bytes. Messages larger than this are fragmented.
|
|
44
|
+
* Set to 0 to disable fragmentation (not recommended for cloud deployments).
|
|
45
|
+
* Default: 100KB
|
|
46
|
+
*/
|
|
47
|
+
fragmentThreshold?: number;
|
|
48
|
+
/** Lifecycle event callbacks. */
|
|
49
|
+
lifecycle?: WebsocketClientLifecycleEvents;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Lifecycle event callbacks for the Websocket client.
|
|
53
|
+
*/
|
|
54
|
+
interface WebsocketClientLifecycleEvents {
|
|
55
|
+
/** Called on every state transition (delivered async via microtask). */
|
|
56
|
+
onStateChange?: (transition: WebsocketClientStateTransition) => void;
|
|
57
|
+
/** Called when the connection is lost. */
|
|
58
|
+
onDisconnect?: (reason: DisconnectReason) => void;
|
|
59
|
+
/** Called when a reconnection attempt is scheduled. */
|
|
60
|
+
onReconnecting?: (attempt: number, nextAttemptMs: number) => void;
|
|
61
|
+
/** Called when reconnection succeeds after a previous connection. */
|
|
62
|
+
onReconnected?: () => void;
|
|
63
|
+
/** Called when the server sends the "ready" signal. */
|
|
64
|
+
onReady?: () => void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Websocket client network transport for @kyneta/exchange.
|
|
68
|
+
*
|
|
69
|
+
* Connects to a Websocket server, sends and receives ChannelMsg via
|
|
70
|
+
* the kyneta wire format (CBOR codec + framing + fragmentation).
|
|
71
|
+
*
|
|
72
|
+
* Internally, the connection lifecycle is a `Program<Msg, Model, Fx>` —
|
|
73
|
+
* a pure Mealy machine whose transitions are deterministically testable.
|
|
74
|
+
* This class is the imperative shell that interprets data effects as I/O.
|
|
75
|
+
*
|
|
76
|
+
* Prefer the factory functions for construction:
|
|
77
|
+
* - `createWebsocketClient()` — browser-to-server (from `./browser`)
|
|
78
|
+
* - `createServiceWebsocketClient()` — service-to-service with headers (from `./server`)
|
|
79
|
+
*/
|
|
80
|
+
declare class WebsocketClientTransport extends Transport<void> {
|
|
81
|
+
#private;
|
|
82
|
+
constructor(options: WebsocketClientOptions);
|
|
83
|
+
/**
|
|
84
|
+
* Get the current connection state.
|
|
85
|
+
*/
|
|
86
|
+
getState(): WebsocketClientState;
|
|
87
|
+
/**
|
|
88
|
+
* Subscribe to state transitions.
|
|
89
|
+
*/
|
|
90
|
+
subscribeToTransitions(listener: TransitionListener$1<WebsocketClientState>): () => void;
|
|
91
|
+
/**
|
|
92
|
+
* Wait for a specific state.
|
|
93
|
+
*/
|
|
94
|
+
waitForState(predicate: (state: WebsocketClientState) => boolean, options?: {
|
|
95
|
+
timeoutMs?: number;
|
|
96
|
+
}): Promise<WebsocketClientState>;
|
|
97
|
+
/**
|
|
98
|
+
* Wait for a specific status.
|
|
99
|
+
*/
|
|
100
|
+
waitForStatus(status: WebsocketClientState["status"], options?: {
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
}): Promise<WebsocketClientState>;
|
|
103
|
+
/**
|
|
104
|
+
* Whether the client is ready (server ready signal received).
|
|
105
|
+
*/
|
|
106
|
+
get isReady(): boolean;
|
|
107
|
+
protected generate(): GeneratedChannel;
|
|
108
|
+
onStart(): Promise<void>;
|
|
109
|
+
onStop(): Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create a Websocket client transport factory for browser-to-server
|
|
113
|
+
* connections.
|
|
114
|
+
*
|
|
115
|
+
* Returns an `TransportFactory` — a closure that creates a fresh transport
|
|
116
|
+
* instance when called. Pass directly to `Exchange({ transports: [...] })`.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* import { createWebsocketClient } from "@kyneta/websocket-transport/browser"
|
|
121
|
+
*
|
|
122
|
+
* const exchange = new Exchange({
|
|
123
|
+
* transports: [createWebsocketClient({
|
|
124
|
+
* url: "ws://localhost:3000/ws",
|
|
125
|
+
* WebSocket,
|
|
126
|
+
* reconnect: { enabled: true },
|
|
127
|
+
* })],
|
|
128
|
+
* })
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function createWebsocketClient(options: WebsocketClientOptions): TransportFactory;
|
|
132
|
+
//#endregion
|
|
133
|
+
export { createWebsocketClient as a, WebsocketClientTransport as i, WebsocketClientLifecycleEvents as n, WebsocketClientOptions as r, DEFAULT_FRAGMENT_THRESHOLD as t };
|
|
134
|
+
//# sourceMappingURL=client-transport-CKjXedwS.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-transport-CKjXedwS.d.ts","names":[],"sources":["../src/client-transport.ts"],"mappings":";;;;;;;;;cA6Da,0BAAA;;;;UAKI,sBAAA;EAoBL;EAlBV,GAAA,aAAgB,MAAA,EAAQ,MAAA;EAuCkB;;;;;;;EA9B1C,SAAA,EAAW,oBAAA;EASD;;;;;;;EAAV,OAAA,GAAU,MAAA;EAqBV;EAlBA,SAAA;IACE,OAAA;IACA,WAAA;IACA,SAAA;IACA,QAAA;EAAA;EAyBsC;EArBxC,iBAAA;EAkB6B;;;;;EAX7B,iBAAA;EAiBkB;EAdlB,SAAA,GAAY,8BAAA;AAAA;;;;UAMG,8BAAA;EAmCqB;EAjCpC,aAAA,IAAiB,UAAA,EAAY,8BAAA;EAmDR;EAhDrB,YAAA,IAAgB,MAAA,EAAQ,gBAAA;EA8XO;EA3X/B,cAAA,IAAkB,OAAA,UAAiB,aAAA;EAoYd;EAjYrB,aAAA;EAmYG;EAhYH,OAAA;AAAA;;;;;;;;;;;;;;;cAqBW,wBAAA,SAAiC,SAAA;EAAA;cAkBhC,OAAA,EAAS,sBAAA;EA8UnB;;;EARF,QAAA,CAAA,GAAY,oBAAA;EAiBV;;;EAVF,sBAAA,CACE,QAAA,EAAU,oBAAA,CAAmB,oBAAA;EAWpB;;;EAHX,YAAA,CACE,SAAA,GAAY,KAAA,EAAO,oBAAA,cACnB,OAAA;IAAY,SAAA;EAAA,IACX,OAAA,CAAQ,oBAAA;EAUR;;;EAHH,aAAA,CACE,MAAA,EAAQ,oBAAA,YACR,OAAA;IAAY,SAAA;EAAA,IACX,OAAA,CAAQ,oBAAA;EAoCM;;;EAAA,IA7Bb,OAAA,CAAA;EAAA,UAQM,QAAA,CAAA,GAAY,gBAAA;EAqBhB,OAAA,CAAA,GAAW,OAAA;EAUX,MAAA,CAAA,GAAU,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;iBA8BF,qBAAA,CACd,OAAA,EAAS,sBAAA,GACR,gBAAA"}
|