@kyneta/websocket-transport 1.1.0 → 1.2.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 +18 -1
- package/dist/bun.d.ts +3 -3
- package/dist/{chunk-5FHT54WT.js → chunk-PSG3LLT5.js} +4 -2
- package/dist/chunk-PSG3LLT5.js.map +1 -0
- package/dist/client.d.ts +72 -50
- package/dist/client.js +381 -291
- package/dist/client.js.map +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +15 -26
- package/dist/server.js.map +1 -1
- package/dist/{types-DG_89zA4.d.ts → types-DdNb8cAz.d.ts} +2 -2
- package/package.json +9 -6
- package/src/__tests__/client-program.test.ts +760 -0
- package/src/client-program.ts +272 -0
- package/src/client-transport.ts +297 -381
- package/src/client.ts +12 -7
- package/src/connection.ts +12 -30
- package/src/server-transport.ts +2 -4
- package/src/types.ts +4 -2
- package/dist/chunk-5FHT54WT.js.map +0 -1
- package/src/__tests__/client-state-machine.test.ts +0 -472
- package/src/client-state-machine.ts +0 -78
package/README.md
CHANGED
|
@@ -132,7 +132,9 @@ const exchange = new Exchange({
|
|
|
132
132
|
|
|
133
133
|
## Connection Lifecycle
|
|
134
134
|
|
|
135
|
-
The
|
|
135
|
+
The connection lifecycle is a `Program<WsClientMsg, WebsocketClientState, WsClientEffect>` from `@kyneta/machine` — a pure Mealy machine with data effects. The transport class (`WebsocketClientTransport`) is a thin imperative shell that interprets data effects as I/O (FC/IS design). Every state × event combination is covered by pure data tests — no sockets, no timing, never flaky.
|
|
136
|
+
|
|
137
|
+
The WebSocket client has a **5-state lifecycle** with an extra `ready` state compared to SSE and Unix Socket transports. The server sends a text `"ready"` signal after the TCP connection opens, and only then does the client create a channel and begin the establishment handshake.
|
|
136
138
|
|
|
137
139
|
```/dev/null/state-machine.txt#L1-8
|
|
138
140
|
disconnected → connecting → connected → ready
|
|
@@ -152,6 +154,19 @@ disconnected → connecting → connected → ready
|
|
|
152
154
|
| `ready` | Server sent `"ready"` text frame — protocol messages can flow. |
|
|
153
155
|
| `reconnecting` | Connection lost, scheduling next attempt. Tracks `attempt` and `nextAttemptMs`. |
|
|
154
156
|
|
|
157
|
+
### Program Architecture
|
|
158
|
+
|
|
159
|
+
The program (`createWsClientProgram`) encodes all transitions and side effects as inspectable data:
|
|
160
|
+
|
|
161
|
+
- **Messages** (`WsClientMsg`): `start`, `socket-opened`, `server-ready`, `socket-closed`, `socket-error`, `reconnect-timer-fired`, `stop`
|
|
162
|
+
- **Effects** (`WsClientEffect`): `create-websocket`, `close-websocket`, `add-channel-and-establish`, `remove-channel`, `start-reconnect-timer`, `cancel-reconnect-timer`, `start-keepalive`, `stop-keepalive`
|
|
163
|
+
|
|
164
|
+
The imperative shell interprets each effect as real I/O — creating `WebSocket` instances, scheduling timers, sending keepalive pings. Keepalive is modeled as `start-keepalive` / `stop-keepalive` effects rather than internal timer logic.
|
|
165
|
+
|
|
166
|
+
### Race Condition Handling
|
|
167
|
+
|
|
168
|
+
The server may send `"ready"` before the client's `open` event fires (server-ready while still `connecting`). The program handles this by transitioning directly from `connecting` → `ready`, skipping the `connected` state entirely.
|
|
169
|
+
|
|
155
170
|
### Connection Handshake
|
|
156
171
|
|
|
157
172
|
1. Client opens Websocket, transitions to `connecting`
|
|
@@ -162,6 +177,8 @@ disconnected → connecting → connected → ready
|
|
|
162
177
|
|
|
163
178
|
### Observing State
|
|
164
179
|
|
|
180
|
+
The public observation API is powered by `createObservableProgram` from `@kyneta/machine`:
|
|
181
|
+
|
|
165
182
|
```/dev/null/observe-state.ts#L1-18
|
|
166
183
|
import { createWebsocketClient } from "@kyneta/websocket-network-adapter/client"
|
|
167
184
|
|
package/dist/bun.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ServerWebSocket } from 'bun';
|
|
2
|
-
import { S as Socket } from './types-
|
|
3
|
-
export { D as DisconnectReason, a as SocketReadyState } from './types-
|
|
4
|
-
import '@kyneta/
|
|
2
|
+
import { S as Socket } from './types-DdNb8cAz.js';
|
|
3
|
+
export { D as DisconnectReason, a as SocketReadyState } from './types-DdNb8cAz.js';
|
|
4
|
+
import '@kyneta/transport';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data structure stored in `ws.data` for handler callbacks.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
function wrapStandardWebsocket(ws) {
|
|
3
3
|
return {
|
|
4
4
|
send(data) {
|
|
5
|
-
ws.send(
|
|
5
|
+
ws.send(
|
|
6
|
+
typeof data === "string" ? data : data
|
|
7
|
+
);
|
|
6
8
|
},
|
|
7
9
|
close(code, reason) {
|
|
8
10
|
ws.close(code, reason);
|
|
@@ -106,4 +108,4 @@ export {
|
|
|
106
108
|
wrapStandardWebsocket,
|
|
107
109
|
wrapNodeWebsocket
|
|
108
110
|
};
|
|
109
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-PSG3LLT5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["// types — framework-agnostic Websocket abstractions for @kyneta/websocket-network-adapter.\n//\n// The `Socket` interface decouples the adapter from any specific Websocket\n// library (browser WebSocket, Node `ws`, Bun ServerWebSocket). Platform-\n// specific wrappers (`wrapStandardWebsocket`, `wrapNodeWebsocket`,\n// `wrapBunWebsocket`) adapt concrete implementations to this interface.\n//\n// Ported from @loro-extended/adapter-websocket's WsSocket with kyneta\n// naming conventions applied.\n\nimport type {\n TransitionListener as GenericTransitionListener,\n PeerId,\n StateTransition,\n} from \"@kyneta/transport\"\n\n// ---------------------------------------------------------------------------\n// Socket ready states\n// ---------------------------------------------------------------------------\n\n/**\n * Websocket ready states — mirrors the standard WebSocket readyState\n * values as human-readable strings.\n */\nexport type SocketReadyState = \"connecting\" | \"open\" | \"closing\" | \"closed\"\n\n// ---------------------------------------------------------------------------\n// Socket interface\n// ---------------------------------------------------------------------------\n\n/**\n * Framework-agnostic Websocket interface.\n *\n * This allows the adapter to work with any Websocket library:\n * - Browser `WebSocket` via `wrapStandardWebsocket()`\n * - Node.js `ws` library via `wrapNodeWebsocket()`\n * - Bun `ServerWebSocket` via `wrapBunWebsocket()`\n *\n * The interface is intentionally minimal — only the operations the\n * adapter needs are exposed.\n */\nexport interface Socket {\n /** Send binary or text data through the Websocket. */\n send(data: Uint8Array | string): void\n\n /** Close the Websocket connection. */\n close(code?: number, reason?: string): void\n\n /** Register a handler for incoming messages (binary or text). */\n onMessage(handler: (data: Uint8Array | string) => void): void\n\n /** Register a handler for connection close. */\n onClose(handler: (code: number, reason: string) => void): void\n\n /** Register a handler for errors. */\n onError(handler: (error: Error) => void): void\n\n /** The current ready state of the Websocket. */\n readonly readyState: SocketReadyState\n}\n\n// ---------------------------------------------------------------------------\n// Connection types — used by server adapter\n// ---------------------------------------------------------------------------\n\n/**\n * Options for handling a new Websocket connection on the server.\n */\nexport interface WebsocketConnectionOptions {\n /** The Websocket instance, wrapped in the Socket interface. */\n socket: Socket\n\n /** Optional peer ID extracted from the upgrade request. */\n peerId?: PeerId\n\n /** Optional authentication token from the upgrade request. */\n authToken?: string\n}\n\n/**\n * Handle for an active Websocket connection.\n */\nexport interface WebsocketConnectionHandle {\n /** The peer ID for this connection. */\n readonly peerId: PeerId\n\n /** The channel ID for this connection. */\n readonly channelId: number\n\n /** Close the connection. */\n close(code?: number, reason?: string): void\n}\n\n/**\n * Result of handling a Websocket connection on the server.\n */\nexport interface WebsocketConnectionResult {\n /** The connection handle for managing this peer. */\n connection: WebsocketConnectionHandle\n\n /** Call this to start processing messages. */\n start(): void\n}\n\n// ---------------------------------------------------------------------------\n// Disconnect reason\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated union describing why a Websocket connection was lost.\n */\nexport type DisconnectReason =\n | { type: \"intentional\" }\n | { type: \"error\"; error: Error }\n | { type: \"closed\"; code: number; reason: string }\n | { type: \"max-retries-exceeded\"; attempts: number }\n | { type: \"not-started\" }\n\n// ---------------------------------------------------------------------------\n// Connection state (for client adapter observability)\n// ---------------------------------------------------------------------------\n\n/**\n * All possible states of the Websocket client.\n *\n * State machine transitions:\n * ```\n * disconnected → connecting → connected → ready\n * ↓ ↓ ↓\n * reconnecting ← ─ ┴ ─ ─ ─ ─ ┘\n * ↓\n * connecting (retry)\n * ↓\n * disconnected (max retries)\n * ```\n */\nexport type WebsocketClientState =\n | { status: \"disconnected\"; reason?: DisconnectReason }\n | { status: \"connecting\"; attempt: number }\n | { status: \"connected\" }\n | { status: \"ready\" }\n | { status: \"reconnecting\"; attempt: number; nextAttemptMs: number }\n\n/**\n * A state transition event for websocket client states.\n * Specialized from the generic `StateTransition<S>`.\n */\nexport type WebsocketClientStateTransition =\n StateTransition<WebsocketClientState>\n\n/**\n * Listener for websocket client state transitions.\n * Specialized from the generic `TransitionListener<S>`.\n */\nexport type TransitionListener = GenericTransitionListener<WebsocketClientState>\n\n// ---------------------------------------------------------------------------\n// Socket wrapper — standard WebSocket API (browser + Node ws)\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a standard `WebSocket` (browser or Node.js `ws` via `ws` package\n * in `WebSocket`-compatible mode) into the `Socket` interface.\n *\n * Handles `ArrayBuffer`, `Blob`, and string messages.\n */\nexport function wrapStandardWebsocket(ws: WebSocket): Socket {\n return {\n send(data: Uint8Array | string): void {\n ws.send(\n typeof data === \"string\" ? data : (data as Uint8Array<ArrayBuffer>),\n )\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.addEventListener(\"message\", event => {\n if (event.data instanceof ArrayBuffer) {\n handler(new Uint8Array(event.data))\n } else if (typeof Blob !== \"undefined\" && event.data instanceof Blob) {\n // Handle Blob data (browser)\n event.data.arrayBuffer().then(buffer => {\n handler(new Uint8Array(buffer))\n })\n } else {\n handler(event.data as string)\n }\n })\n },\n\n onClose(handler: (code: number, reason: string) => void): void {\n ws.addEventListener(\"close\", event => {\n handler(event.code, event.reason)\n })\n },\n\n onError(handler: (error: Error) => void): void {\n ws.addEventListener(\"error\", _event => {\n handler(new Error(\"WebSocket error\"))\n })\n },\n\n get readyState(): SocketReadyState {\n switch (ws.readyState) {\n case WebSocket.CONNECTING:\n return \"connecting\"\n case WebSocket.OPEN:\n return \"open\"\n case WebSocket.CLOSING:\n return \"closing\"\n case WebSocket.CLOSED:\n return \"closed\"\n default:\n return \"closed\"\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Socket wrapper — Node.js `ws` library (raw API, not WebSocket-compat)\n// ---------------------------------------------------------------------------\n\n/**\n * The minimal interface we need from the Node.js `ws` library's `WebSocket`.\n *\n * Using a structural type rather than importing `ws` — consumers provide\n * the actual `ws` instance, we just need these methods.\n */\nexport interface NodeWebsocketLike {\n send(data: Uint8Array | string): void\n close(code?: number, reason?: string): void\n on(\n event: \"message\",\n handler: (data: Buffer | ArrayBuffer | string, isBinary: boolean) => void,\n ): void\n on(event: \"close\", handler: (code: number, reason: Buffer) => void): void\n on(event: \"error\", handler: (error: Error) => void): void\n readyState: number\n}\n\n/**\n * Wrap a Node.js `ws` library WebSocket into the `Socket` interface.\n *\n * Handles `Buffer` → `Uint8Array` conversion for binary messages.\n */\nexport function wrapNodeWebsocket(ws: NodeWebsocketLike): Socket {\n const CONNECTING = 0\n const OPEN = 1\n const CLOSING = 2\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.on(\n \"message\",\n (data: Buffer | ArrayBuffer | string, isBinary: boolean) => {\n if (isBinary) {\n if (data instanceof ArrayBuffer) {\n handler(new Uint8Array(data))\n } else if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data)) {\n handler(new Uint8Array(data))\n } else {\n handler(new Uint8Array(data as unknown as ArrayBuffer))\n }\n } else {\n if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data)) {\n handler(data.toString(\"utf-8\"))\n } else {\n handler(data as string)\n }\n }\n },\n )\n },\n\n onClose(handler: (code: number, reason: string) => void): void {\n ws.on(\"close\", (code: number, reason: Buffer) => {\n handler(code, reason.toString())\n })\n },\n\n onError(handler: (error: Error) => void): void {\n ws.on(\"error\", handler)\n },\n\n get readyState(): SocketReadyState {\n switch (ws.readyState) {\n case CONNECTING:\n return \"connecting\"\n case OPEN:\n return \"open\"\n case CLOSING:\n return \"closing\"\n default:\n return \"closed\"\n }\n },\n }\n}\n"],"mappings":";AAsKO,SAAS,sBAAsB,IAAuB;AAC3D,SAAO;AAAA,IACL,KAAK,MAAiC;AACpC,SAAG;AAAA,QACD,OAAO,SAAS,WAAW,OAAQ;AAAA,MACrC;AAAA,IACF;AAAA,IAEA,MAAM,MAAe,QAAuB;AAC1C,SAAG,MAAM,MAAM,MAAM;AAAA,IACvB;AAAA,IAEA,UAAU,SAAoD;AAC5D,SAAG,iBAAiB,WAAW,WAAS;AACtC,YAAI,MAAM,gBAAgB,aAAa;AACrC,kBAAQ,IAAI,WAAW,MAAM,IAAI,CAAC;AAAA,QACpC,WAAW,OAAO,SAAS,eAAe,MAAM,gBAAgB,MAAM;AAEpE,gBAAM,KAAK,YAAY,EAAE,KAAK,YAAU;AACtC,oBAAQ,IAAI,WAAW,MAAM,CAAC;AAAA,UAChC,CAAC;AAAA,QACH,OAAO;AACL,kBAAQ,MAAM,IAAc;AAAA,QAC9B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,SAAuD;AAC7D,SAAG,iBAAiB,SAAS,WAAS;AACpC,gBAAQ,MAAM,MAAM,MAAM,MAAM;AAAA,MAClC,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,SAAuC;AAC7C,SAAG,iBAAiB,SAAS,YAAU;AACrC,gBAAQ,IAAI,MAAM,iBAAiB,CAAC;AAAA,MACtC,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,aAA+B;AACjC,cAAQ,GAAG,YAAY;AAAA,QACrB,KAAK,UAAU;AACb,iBAAO;AAAA,QACT,KAAK,UAAU;AACb,iBAAO;AAAA,QACT,KAAK,UAAU;AACb,iBAAO;AAAA,QACT,KAAK,UAAU;AACb,iBAAO;AAAA,QACT;AACE,iBAAO;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;AA6BO,SAAS,kBAAkB,IAA+B;AAC/D,QAAM,aAAa;AACnB,QAAM,OAAO;AACb,QAAM,UAAU;AAEhB,SAAO;AAAA,IACL,KAAK,MAAiC;AACpC,SAAG,KAAK,IAAI;AAAA,IACd;AAAA,IAEA,MAAM,MAAe,QAAuB;AAC1C,SAAG,MAAM,MAAM,MAAM;AAAA,IACvB;AAAA,IAEA,UAAU,SAAoD;AAC5D,SAAG;AAAA,QACD;AAAA,QACA,CAAC,MAAqC,aAAsB;AAC1D,cAAI,UAAU;AACZ,gBAAI,gBAAgB,aAAa;AAC/B,sBAAQ,IAAI,WAAW,IAAI,CAAC;AAAA,YAC9B,WAAW,OAAO,WAAW,eAAe,OAAO,SAAS,IAAI,GAAG;AACjE,sBAAQ,IAAI,WAAW,IAAI,CAAC;AAAA,YAC9B,OAAO;AACL,sBAAQ,IAAI,WAAW,IAA8B,CAAC;AAAA,YACxD;AAAA,UACF,OAAO;AACL,gBAAI,OAAO,WAAW,eAAe,OAAO,SAAS,IAAI,GAAG;AAC1D,sBAAQ,KAAK,SAAS,OAAO,CAAC;AAAA,YAChC,OAAO;AACL,sBAAQ,IAAc;AAAA,YACxB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,QAAQ,SAAuD;AAC7D,SAAG,GAAG,SAAS,CAAC,MAAc,WAAmB;AAC/C,gBAAQ,MAAM,OAAO,SAAS,CAAC;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,SAAuC;AAC7C,SAAG,GAAG,SAAS,OAAO;AAAA,IACxB;AAAA,IAEA,IAAI,aAA+B;AACjC,cAAQ,GAAG,YAAY;AAAA,QACrB,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT;AACE,iBAAO;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { Program, TransitionListener } from '@kyneta/machine';
|
|
2
|
+
import { ReconnectOptions, PeerId, Transport, GeneratedChannel, TransportFactory } from '@kyneta/transport';
|
|
3
|
+
import { W as WebsocketClientState, b as WebsocketClientStateTransition, D as DisconnectReason } from './types-DdNb8cAz.js';
|
|
4
|
+
export { S as Socket, a as SocketReadyState, T as TransitionListener, w as wrapStandardWebsocket } from './types-DdNb8cAz.js';
|
|
5
|
+
|
|
6
|
+
type WsClientMsg = {
|
|
7
|
+
type: "start";
|
|
8
|
+
} | {
|
|
9
|
+
type: "socket-opened";
|
|
10
|
+
} | {
|
|
11
|
+
type: "server-ready";
|
|
12
|
+
} | {
|
|
13
|
+
type: "socket-closed";
|
|
14
|
+
code: number;
|
|
15
|
+
reason: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "socket-error";
|
|
18
|
+
error: Error;
|
|
19
|
+
} | {
|
|
20
|
+
type: "reconnect-timer-fired";
|
|
21
|
+
} | {
|
|
22
|
+
type: "stop";
|
|
23
|
+
};
|
|
24
|
+
type WsClientEffect = {
|
|
25
|
+
type: "create-websocket";
|
|
26
|
+
attempt: number;
|
|
27
|
+
} | {
|
|
28
|
+
type: "close-websocket";
|
|
29
|
+
} | {
|
|
30
|
+
type: "add-channel-and-establish";
|
|
31
|
+
} | {
|
|
32
|
+
type: "remove-channel";
|
|
33
|
+
} | {
|
|
34
|
+
type: "start-reconnect-timer";
|
|
35
|
+
delayMs: number;
|
|
36
|
+
} | {
|
|
37
|
+
type: "cancel-reconnect-timer";
|
|
38
|
+
} | {
|
|
39
|
+
type: "start-keepalive";
|
|
40
|
+
} | {
|
|
41
|
+
type: "stop-keepalive";
|
|
42
|
+
};
|
|
43
|
+
interface WsClientProgramOptions {
|
|
44
|
+
reconnect?: Partial<ReconnectOptions>;
|
|
45
|
+
/** Inject jitter source for deterministic testing. Default: () => Math.random() * 1000 */
|
|
46
|
+
jitterFn?: () => number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create the websocket client connection lifecycle program — a pure Mealy machine.
|
|
50
|
+
*
|
|
51
|
+
* The returned `Program<WsClientMsg, WebsocketClientState, WsClientEffect>`
|
|
52
|
+
* encodes every state transition and effect as inspectable data. The imperative
|
|
53
|
+
* shell interprets `WsClientEffect` as actual I/O.
|
|
54
|
+
*/
|
|
55
|
+
declare function createWsClientProgram(options?: WsClientProgramOptions): Program<WsClientMsg, WebsocketClientState, WsClientEffect>;
|
|
4
56
|
|
|
5
57
|
/**
|
|
6
58
|
* Default fragment threshold in bytes.
|
|
@@ -8,7 +60,7 @@ export { S as Socket, a as SocketReadyState, w as wrapStandardWebsocket } from '
|
|
|
8
60
|
*/
|
|
9
61
|
declare const DEFAULT_FRAGMENT_THRESHOLD: number;
|
|
10
62
|
/**
|
|
11
|
-
* Options for the Websocket client
|
|
63
|
+
* Options for the Websocket client transport (browser connections).
|
|
12
64
|
*/
|
|
13
65
|
interface WebsocketClientOptions {
|
|
14
66
|
/** Websocket URL to connect to. Can be a string or a function of peerId. */
|
|
@@ -17,7 +69,7 @@ interface WebsocketClientOptions {
|
|
|
17
69
|
WebSocket?: typeof globalThis.WebSocket;
|
|
18
70
|
/** Reconnection options. */
|
|
19
71
|
reconnect?: {
|
|
20
|
-
enabled
|
|
72
|
+
enabled?: boolean;
|
|
21
73
|
maxAttempts?: number;
|
|
22
74
|
baseDelay?: number;
|
|
23
75
|
maxDelay?: number;
|
|
@@ -63,11 +115,15 @@ interface ServiceWebsocketClientOptions extends WebsocketClientOptions {
|
|
|
63
115
|
headers?: Record<string, string>;
|
|
64
116
|
}
|
|
65
117
|
/**
|
|
66
|
-
* Websocket client network
|
|
118
|
+
* Websocket client network transport for @kyneta/exchange.
|
|
67
119
|
*
|
|
68
120
|
* Connects to a Websocket server, sends and receives ChannelMsg via
|
|
69
121
|
* the kyneta wire format (CBOR codec + framing + fragmentation).
|
|
70
122
|
*
|
|
123
|
+
* Internally, the connection lifecycle is a `Program<Msg, Model, Fx>` —
|
|
124
|
+
* a pure Mealy machine whose transitions are deterministically testable.
|
|
125
|
+
* This class is the imperative shell that interprets data effects as I/O.
|
|
126
|
+
*
|
|
71
127
|
* Prefer the factory functions for construction:
|
|
72
128
|
* - `createWebsocketClient()` — browser-to-server
|
|
73
129
|
* - `createServiceWebsocketClient()` — service-to-service (with headers)
|
|
@@ -76,14 +132,13 @@ declare class WebsocketClientTransport extends Transport<void> {
|
|
|
76
132
|
#private;
|
|
77
133
|
constructor(options: ServiceWebsocketClientOptions);
|
|
78
134
|
/**
|
|
79
|
-
* Get the current state
|
|
135
|
+
* Get the current connection state.
|
|
80
136
|
*/
|
|
81
137
|
getState(): WebsocketClientState;
|
|
82
138
|
/**
|
|
83
139
|
* Subscribe to state transitions.
|
|
84
|
-
* @returns Unsubscribe function
|
|
85
140
|
*/
|
|
86
|
-
subscribeToTransitions(listener: TransitionListener): () => void;
|
|
141
|
+
subscribeToTransitions(listener: TransitionListener<WebsocketClientState>): () => void;
|
|
87
142
|
/**
|
|
88
143
|
* Wait for a specific state.
|
|
89
144
|
*/
|
|
@@ -97,7 +152,7 @@ declare class WebsocketClientTransport extends Transport<void> {
|
|
|
97
152
|
timeoutMs?: number;
|
|
98
153
|
}): Promise<WebsocketClientState>;
|
|
99
154
|
/**
|
|
100
|
-
*
|
|
155
|
+
* Whether the client is ready (server ready signal received).
|
|
101
156
|
*/
|
|
102
157
|
get isReady(): boolean;
|
|
103
158
|
protected generate(): GeneratedChannel;
|
|
@@ -105,14 +160,15 @@ declare class WebsocketClientTransport extends Transport<void> {
|
|
|
105
160
|
onStop(): Promise<void>;
|
|
106
161
|
}
|
|
107
162
|
/**
|
|
108
|
-
* Create a Websocket client
|
|
163
|
+
* Create a Websocket client transport factory for browser-to-server
|
|
164
|
+
* connections.
|
|
109
165
|
*
|
|
110
|
-
* Returns an `TransportFactory` — a closure that creates a fresh
|
|
166
|
+
* Returns an `TransportFactory` — a closure that creates a fresh transport
|
|
111
167
|
* instance when called. Pass directly to `Exchange({ transports: [...] })`.
|
|
112
168
|
*
|
|
113
169
|
* @example
|
|
114
170
|
* ```typescript
|
|
115
|
-
* import { createWebsocketClient } from "@kyneta/websocket-
|
|
171
|
+
* import { createWebsocketClient } from "@kyneta/websocket-transport/client"
|
|
116
172
|
*
|
|
117
173
|
* const exchange = new Exchange({
|
|
118
174
|
* transports: [createWebsocketClient({
|
|
@@ -124,7 +180,7 @@ declare class WebsocketClientTransport extends Transport<void> {
|
|
|
124
180
|
*/
|
|
125
181
|
declare function createWebsocketClient(options: WebsocketClientOptions): TransportFactory;
|
|
126
182
|
/**
|
|
127
|
-
* Create a Websocket client
|
|
183
|
+
* Create a Websocket client transport for service-to-service connections.
|
|
128
184
|
*
|
|
129
185
|
* This factory is for backend environments (Bun, Node.js) where you need
|
|
130
186
|
* to pass authentication headers during the Websocket upgrade.
|
|
@@ -135,7 +191,7 @@ declare function createWebsocketClient(options: WebsocketClientOptions): Transpo
|
|
|
135
191
|
*
|
|
136
192
|
* @example
|
|
137
193
|
* ```typescript
|
|
138
|
-
* import { createServiceWebsocketClient } from "@kyneta/websocket-
|
|
194
|
+
* import { createServiceWebsocketClient } from "@kyneta/websocket-transport/client"
|
|
139
195
|
*
|
|
140
196
|
* const exchange = new Exchange({
|
|
141
197
|
* transports: [createServiceWebsocketClient({
|
|
@@ -148,38 +204,4 @@ declare function createWebsocketClient(options: WebsocketClientOptions): Transpo
|
|
|
148
204
|
*/
|
|
149
205
|
declare function createServiceWebsocketClient(options: ServiceWebsocketClientOptions): TransportFactory;
|
|
150
206
|
|
|
151
|
-
|
|
152
|
-
* Observable state machine for Websocket client connection lifecycle.
|
|
153
|
-
*
|
|
154
|
-
* Extends the generic `ClientStateMachine<WebsocketClientState>` with
|
|
155
|
-
* websocket-specific convenience helpers.
|
|
156
|
-
*
|
|
157
|
-
* Usage:
|
|
158
|
-
* ```typescript
|
|
159
|
-
* const sm = new WebsocketClientStateMachine()
|
|
160
|
-
*
|
|
161
|
-
* sm.subscribeToTransitions(({ from, to }) => {
|
|
162
|
-
* console.log(`${from.status} → ${to.status}`)
|
|
163
|
-
* })
|
|
164
|
-
*
|
|
165
|
-
* sm.transition({ status: "connecting", attempt: 1 })
|
|
166
|
-
* sm.transition({ status: "connected" })
|
|
167
|
-
* sm.transition({ status: "ready" })
|
|
168
|
-
*
|
|
169
|
-
* // Transitions are delivered asynchronously via microtask
|
|
170
|
-
* // Listener will see: disconnected → connecting, connecting → connected, connected → ready
|
|
171
|
-
* ```
|
|
172
|
-
*/
|
|
173
|
-
declare class WebsocketClientStateMachine extends ClientStateMachine<WebsocketClientState> {
|
|
174
|
-
constructor();
|
|
175
|
-
/**
|
|
176
|
-
* Check if the client is in a "connected" state (either connected or ready).
|
|
177
|
-
*/
|
|
178
|
-
isConnectedOrReady(): boolean;
|
|
179
|
-
/**
|
|
180
|
-
* Check if the client is ready (server ready signal received).
|
|
181
|
-
*/
|
|
182
|
-
isReady(): boolean;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export { DEFAULT_FRAGMENT_THRESHOLD, DisconnectReason, type ServiceWebsocketClientOptions, TransitionListener, type WebsocketClientLifecycleEvents, type WebsocketClientOptions, WebsocketClientState, WebsocketClientStateMachine, WebsocketClientStateTransition, WebsocketClientTransport, createServiceWebsocketClient, createWebsocketClient };
|
|
207
|
+
export { DEFAULT_FRAGMENT_THRESHOLD, DisconnectReason, type ServiceWebsocketClientOptions, type WebsocketClientLifecycleEvents, type WebsocketClientOptions, WebsocketClientState, WebsocketClientStateTransition, WebsocketClientTransport, type WsClientEffect, type WsClientMsg, type WsClientProgramOptions, createServiceWebsocketClient, createWebsocketClient, createWsClientProgram };
|