@marianmeres/ws 0.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/dist/mod.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `@marianmeres/ws` — a WebSocket client with namespaces, rooms, presence and
3
+ * auto-reconnect.
4
+ *
5
+ * The client is dependency-light and runs in browsers, Deno, Node 22+, Bun and
6
+ * Workers — `WebSocket` is a global in all of them, so there is no polyfill and
7
+ * no transport dependency.
8
+ *
9
+ * The reference server lives behind a separate entry point,
10
+ * `@marianmeres/ws/server`, so `demino` never lands in a browser bundle.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { createWSClient } from "@marianmeres/ws";
15
+ *
16
+ * const ws = createWSClient({ url: "/ws", namespace: "org-123" });
17
+ *
18
+ * const unsub = await ws.subscribe("chat", (msg) => {
19
+ * console.log(msg.from, msg.payload);
20
+ * });
21
+ *
22
+ * await ws.publish("chat", { text: "hello" });
23
+ * ```
24
+ *
25
+ * @module
26
+ */
27
+ export { createWSClient, type SubscribeOptions, WSClient, type WSClientOptions, type WSConnectionState, type WSEvents, type WSState, } from "./client/ws-client.js";
28
+ export type { MessageHandler, PresenceHandler } from "./client/rooms.js";
29
+ export { backoffDelay } from "./client/backoff.js";
30
+ export * from "./protocol/mod.js";
package/dist/mod.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `@marianmeres/ws` — a WebSocket client with namespaces, rooms, presence and
3
+ * auto-reconnect.
4
+ *
5
+ * The client is dependency-light and runs in browsers, Deno, Node 22+, Bun and
6
+ * Workers — `WebSocket` is a global in all of them, so there is no polyfill and
7
+ * no transport dependency.
8
+ *
9
+ * The reference server lives behind a separate entry point,
10
+ * `@marianmeres/ws/server`, so `demino` never lands in a browser bundle.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { createWSClient } from "@marianmeres/ws";
15
+ *
16
+ * const ws = createWSClient({ url: "/ws", namespace: "org-123" });
17
+ *
18
+ * const unsub = await ws.subscribe("chat", (msg) => {
19
+ * console.log(msg.from, msg.payload);
20
+ * });
21
+ *
22
+ * await ws.publish("chat", { text: "hello" });
23
+ * ```
24
+ *
25
+ * @module
26
+ */
27
+ export { createWSClient, WSClient, } from "./client/ws-client.js";
28
+ export { backoffDelay } from "./client/backoff.js";
29
+ export * from "./protocol/mod.js";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Wire protocol constants shared by client and server.
3
+ *
4
+ * This module is intentionally dependency free — it is the single source of
5
+ * truth both sides import, which is what keeps them from drifting apart.
6
+ *
7
+ * @module
8
+ */
9
+ /**
10
+ * Protocol version, announced by the server in its `hello` frame.
11
+ *
12
+ * It costs nothing today and it is the only thing that makes a breaking
13
+ * protocol change survivable later.
14
+ */
15
+ export declare const PROTOCOL_VERSION = 1;
16
+ /** Namespace used when the client does not specify one. */
17
+ export declare const DEFAULT_NAMESPACE = "default";
18
+ /**
19
+ * Frame type discriminators.
20
+ *
21
+ * Note these are *protocol* types and have nothing to do with whatever the
22
+ * application puts inside `payload` — the payload is opaque and is never
23
+ * inspected nor mutated by this library.
24
+ */
25
+ export declare const FRAME: {
26
+ /** Handshake. Always the first frame; carries the auth payload. */
27
+ readonly AUTH: "auth";
28
+ /** Join one or more rooms, optionally with presence. */
29
+ readonly SUB: "sub";
30
+ /** Leave one or more rooms. */
31
+ readonly UNSUB: "unsub";
32
+ /** Publish into a room within the connection's own namespace. */
33
+ readonly PUB: "pub";
34
+ /** Publish into a room across every namespace. Gated server-side. */
35
+ readonly BROADCAST: "broadcast";
36
+ /** Liveness probe. Answered with `pong`. */
37
+ readonly PING: "ping";
38
+ /** Handshake accepted; carries the assigned id, namespace and version. */
39
+ readonly HELLO: "hello";
40
+ /** Positive acknowledgement of a correlated request. */
41
+ readonly ACK: "ack";
42
+ /** Negative acknowledgement; carries a `WSErrorInfo`. */
43
+ readonly NACK: "nack";
44
+ /** A message delivered to a subscribed room. */
45
+ readonly MSG: "msg";
46
+ /** A membership change in a presence-enabled room. */
47
+ readonly PRESENCE: "presence";
48
+ /** Reply to `ping`. */
49
+ readonly PONG: "pong";
50
+ /** Uncorrelated error — not tied to any request id. */
51
+ readonly ERROR: "error";
52
+ };
53
+ /**
54
+ * WebSocket close codes.
55
+ *
56
+ * The `4xxx` range is reserved for application use by RFC 6455.
57
+ */
58
+ export declare const CLOSE: {
59
+ /** Normal closure. Server-sent means "restarting" — the client reconnects. */
60
+ readonly NORMAL: 1000;
61
+ /** Endpoint going away (shutdown, navigation). Recoverable. */
62
+ readonly GOING_AWAY: 1001;
63
+ /** No close frame received. The everyday network drop. */
64
+ readonly ABNORMAL: 1006;
65
+ /** Unexpected server-side condition. Recoverable. */
66
+ readonly INTERNAL_ERROR: 1011;
67
+ /** Authentication rejected. Terminal — retrying cannot help. */
68
+ readonly AUTH_FAILED: 4001;
69
+ /** Client did not send an `auth` frame in time. Recoverable. */
70
+ readonly AUTH_TIMEOUT: 4002;
71
+ /** Authenticated but not permitted. Terminal. */
72
+ readonly FORBIDDEN: 4003;
73
+ /** Connection went silent and was reaped. Recoverable. */
74
+ readonly IDLE_TIMEOUT: 4008;
75
+ /** Too many frames per second. Recoverable, with a longer backoff floor. */
76
+ readonly RATE_LIMITED: 4009;
77
+ /** Frame exceeded `maxFrameSize`. Recoverable. */
78
+ readonly FRAME_TOO_LARGE: 4013;
79
+ /** Malformed frame or codec mismatch. Recoverable (but likely a config bug). */
80
+ readonly PROTOCOL_ERROR: 4400;
81
+ /** Local, client-initiated teardown. Never reconnects by definition. */
82
+ readonly CLIENT_GONE: 4900;
83
+ };
84
+ /**
85
+ * Close codes after which reconnecting is pointless.
86
+ *
87
+ * Everything *not* in this list reconnects — including a server-sent `1000`,
88
+ * because a graceful shutdown or rolling deploy is exactly when clients must
89
+ * come back.
90
+ */
91
+ export declare const DEFAULT_TERMINAL_CLOSE_CODES: readonly number[];
92
+ /** Application-level error codes carried in `nack`/`error` frames. */
93
+ export declare const ERROR_CODE: {
94
+ /** Operation attempted before the handshake completed. */
95
+ readonly UNAUTHORIZED: "unauthorized";
96
+ /** Authenticated, but not permitted — e.g. a denied broadcast. */
97
+ readonly FORBIDDEN: "forbidden";
98
+ /** Malformed or nonsensical frame. */
99
+ readonly BAD_REQUEST: "bad_request";
100
+ /** Frame rate cap exceeded. */
101
+ readonly RATE_LIMITED: "rate_limited";
102
+ /** Unexpected server-side failure. */
103
+ readonly INTERNAL: "internal";
104
+ };
105
+ /** Presence event kinds. */
106
+ export declare const PRESENCE: {
107
+ /** Full membership snapshot, sent on every (re)subscribe. */
108
+ readonly SYNC: "sync";
109
+ /** A client joined the room. Delta — `clientId` is the joiner. */
110
+ readonly JOIN: "join";
111
+ /** A client left the room. Delta — `clientId` is the leaver. */
112
+ readonly LEAVE: "leave";
113
+ };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Wire protocol constants shared by client and server.
3
+ *
4
+ * This module is intentionally dependency free — it is the single source of
5
+ * truth both sides import, which is what keeps them from drifting apart.
6
+ *
7
+ * @module
8
+ */
9
+ /**
10
+ * Protocol version, announced by the server in its `hello` frame.
11
+ *
12
+ * It costs nothing today and it is the only thing that makes a breaking
13
+ * protocol change survivable later.
14
+ */
15
+ export const PROTOCOL_VERSION = 1;
16
+ /** Namespace used when the client does not specify one. */
17
+ export const DEFAULT_NAMESPACE = "default";
18
+ /**
19
+ * Frame type discriminators.
20
+ *
21
+ * Note these are *protocol* types and have nothing to do with whatever the
22
+ * application puts inside `payload` — the payload is opaque and is never
23
+ * inspected nor mutated by this library.
24
+ */
25
+ export const FRAME = {
26
+ // client -> server
27
+ /** Handshake. Always the first frame; carries the auth payload. */
28
+ AUTH: "auth",
29
+ /** Join one or more rooms, optionally with presence. */
30
+ SUB: "sub",
31
+ /** Leave one or more rooms. */
32
+ UNSUB: "unsub",
33
+ /** Publish into a room within the connection's own namespace. */
34
+ PUB: "pub",
35
+ /** Publish into a room across every namespace. Gated server-side. */
36
+ BROADCAST: "broadcast",
37
+ /** Liveness probe. Answered with `pong`. */
38
+ PING: "ping",
39
+ // server -> client
40
+ /** Handshake accepted; carries the assigned id, namespace and version. */
41
+ HELLO: "hello",
42
+ /** Positive acknowledgement of a correlated request. */
43
+ ACK: "ack",
44
+ /** Negative acknowledgement; carries a `WSErrorInfo`. */
45
+ NACK: "nack",
46
+ /** A message delivered to a subscribed room. */
47
+ MSG: "msg",
48
+ /** A membership change in a presence-enabled room. */
49
+ PRESENCE: "presence",
50
+ /** Reply to `ping`. */
51
+ PONG: "pong",
52
+ /** Uncorrelated error — not tied to any request id. */
53
+ ERROR: "error",
54
+ };
55
+ /**
56
+ * WebSocket close codes.
57
+ *
58
+ * The `4xxx` range is reserved for application use by RFC 6455.
59
+ */
60
+ export const CLOSE = {
61
+ /** Normal closure. Server-sent means "restarting" — the client reconnects. */
62
+ NORMAL: 1000,
63
+ /** Endpoint going away (shutdown, navigation). Recoverable. */
64
+ GOING_AWAY: 1001,
65
+ /** No close frame received. The everyday network drop. */
66
+ ABNORMAL: 1006,
67
+ /** Unexpected server-side condition. Recoverable. */
68
+ INTERNAL_ERROR: 1011,
69
+ /** Authentication rejected. Terminal — retrying cannot help. */
70
+ AUTH_FAILED: 4001,
71
+ /** Client did not send an `auth` frame in time. Recoverable. */
72
+ AUTH_TIMEOUT: 4002,
73
+ /** Authenticated but not permitted. Terminal. */
74
+ FORBIDDEN: 4003,
75
+ /** Connection went silent and was reaped. Recoverable. */
76
+ IDLE_TIMEOUT: 4008,
77
+ /** Too many frames per second. Recoverable, with a longer backoff floor. */
78
+ RATE_LIMITED: 4009,
79
+ /** Frame exceeded `maxFrameSize`. Recoverable. */
80
+ FRAME_TOO_LARGE: 4013,
81
+ /** Malformed frame or codec mismatch. Recoverable (but likely a config bug). */
82
+ PROTOCOL_ERROR: 4400,
83
+ /** Local, client-initiated teardown. Never reconnects by definition. */
84
+ CLIENT_GONE: 4900,
85
+ };
86
+ /**
87
+ * Close codes after which reconnecting is pointless.
88
+ *
89
+ * Everything *not* in this list reconnects — including a server-sent `1000`,
90
+ * because a graceful shutdown or rolling deploy is exactly when clients must
91
+ * come back.
92
+ */
93
+ export const DEFAULT_TERMINAL_CLOSE_CODES = [
94
+ CLOSE.AUTH_FAILED,
95
+ CLOSE.FORBIDDEN,
96
+ ];
97
+ /** Application-level error codes carried in `nack`/`error` frames. */
98
+ export const ERROR_CODE = {
99
+ /** Operation attempted before the handshake completed. */
100
+ UNAUTHORIZED: "unauthorized",
101
+ /** Authenticated, but not permitted — e.g. a denied broadcast. */
102
+ FORBIDDEN: "forbidden",
103
+ /** Malformed or nonsensical frame. */
104
+ BAD_REQUEST: "bad_request",
105
+ /** Frame rate cap exceeded. */
106
+ RATE_LIMITED: "rate_limited",
107
+ /** Unexpected server-side failure. */
108
+ INTERNAL: "internal",
109
+ };
110
+ /** Presence event kinds. */
111
+ export const PRESENCE = {
112
+ /** Full membership snapshot, sent on every (re)subscribe. */
113
+ SYNC: "sync",
114
+ /** A client joined the room. Delta — `clientId` is the joiner. */
115
+ JOIN: "join",
116
+ /** A client left the room. Delta — `clientId` is the leaver. */
117
+ LEAVE: "leave",
118
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Typed errors.
3
+ *
4
+ * Every rejection this library produces is one of these, so callers can branch
5
+ * on `instanceof` rather than string-matching messages.
6
+ *
7
+ * @module
8
+ */
9
+ import type { WSErrorInfo } from "./frames.js";
10
+ /**
11
+ * Base class for everything thrown by this library.
12
+ *
13
+ * `name` is set from the concrete subclass, so it survives bundling and reads
14
+ * correctly in logs.
15
+ */
16
+ export declare class WSError extends Error {
17
+ /**
18
+ * Creates the error and stamps `name` from the concrete subclass.
19
+ *
20
+ * @param message - human-readable description
21
+ */
22
+ constructor(message: string);
23
+ }
24
+ /**
25
+ * The connection reached a terminal state and will not retry.
26
+ *
27
+ * The only path by which an infinitely-retrying client gives up, which is why
28
+ * it is logged at error level and rejects any pending `connect()`.
29
+ */
30
+ export declare class WSTerminatedError extends WSError {
31
+ /** WebSocket close code that caused the termination. */
32
+ readonly code: number;
33
+ /** Close reason as sent by the peer. May be an empty string. */
34
+ readonly reason: string;
35
+ /**
36
+ * Built from the close event that ended the connection.
37
+ *
38
+ * @param code - the close code that ended it
39
+ * @param reason - the close reason, possibly empty
40
+ */
41
+ constructor(
42
+ /** WebSocket close code that caused the termination. */
43
+ code: number,
44
+ /** Close reason as sent by the peer. May be an empty string. */
45
+ reason: string);
46
+ }
47
+ /**
48
+ * `connectTimeout` elapsed before the first successful connect.
49
+ *
50
+ * Note the client keeps retrying in the background — this rejection bounds
51
+ * *your await*, not the connection attempt.
52
+ */
53
+ export declare class WSConnectTimeoutError extends WSError {
54
+ /**
55
+ * Reports the deadline that was exceeded.
56
+ *
57
+ * @param ms - the elapsed `connectTimeout`
58
+ */
59
+ constructor(ms: number);
60
+ }
61
+ /** `sendTimeout` elapsed while queued, in flight, or awaiting an ack. */
62
+ export declare class WSTimeoutError extends WSError {
63
+ /**
64
+ * Reports the deadline that was exceeded.
65
+ *
66
+ * @param ms - the elapsed `sendTimeout`
67
+ */
68
+ constructor(ms: number);
69
+ }
70
+ /** The frame was evicted from a full outbox before it could be sent. */
71
+ export declare class WSOutboxDropError extends WSError {
72
+ constructor();
73
+ }
74
+ /** The server rejected the operation with a `nack`. */
75
+ export declare class WSRemoteError extends WSError {
76
+ /** Machine-readable code from the server — see `ERROR_CODE`. */
77
+ readonly code: string;
78
+ /**
79
+ * Lifts a wire-level error detail into a throwable.
80
+ *
81
+ * @param info - the error detail carried by the `nack` frame
82
+ */
83
+ constructor(info: WSErrorInfo);
84
+ }
85
+ /** The client was disposed while the operation was pending. */
86
+ export declare class WSDisposedError extends WSError {
87
+ constructor();
88
+ }
89
+ /** Sending was attempted while disconnected with buffering disabled. */
90
+ export declare class WSNotConnectedError extends WSError {
91
+ constructor();
92
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Typed errors.
3
+ *
4
+ * Every rejection this library produces is one of these, so callers can branch
5
+ * on `instanceof` rather than string-matching messages.
6
+ *
7
+ * @module
8
+ */
9
+ /**
10
+ * Base class for everything thrown by this library.
11
+ *
12
+ * `name` is set from the concrete subclass, so it survives bundling and reads
13
+ * correctly in logs.
14
+ */
15
+ export class WSError extends Error {
16
+ /**
17
+ * Creates the error and stamps `name` from the concrete subclass.
18
+ *
19
+ * @param message - human-readable description
20
+ */
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = new.target.name;
24
+ }
25
+ }
26
+ /**
27
+ * The connection reached a terminal state and will not retry.
28
+ *
29
+ * The only path by which an infinitely-retrying client gives up, which is why
30
+ * it is logged at error level and rejects any pending `connect()`.
31
+ */
32
+ export class WSTerminatedError extends WSError {
33
+ code;
34
+ reason;
35
+ /**
36
+ * Built from the close event that ended the connection.
37
+ *
38
+ * @param code - the close code that ended it
39
+ * @param reason - the close reason, possibly empty
40
+ */
41
+ constructor(
42
+ /** WebSocket close code that caused the termination. */
43
+ code,
44
+ /** Close reason as sent by the peer. May be an empty string. */
45
+ reason) {
46
+ super(`Connection terminated (${code})${reason ? `: ${reason}` : ""}`);
47
+ this.code = code;
48
+ this.reason = reason;
49
+ }
50
+ }
51
+ /**
52
+ * `connectTimeout` elapsed before the first successful connect.
53
+ *
54
+ * Note the client keeps retrying in the background — this rejection bounds
55
+ * *your await*, not the connection attempt.
56
+ */
57
+ export class WSConnectTimeoutError extends WSError {
58
+ /**
59
+ * Reports the deadline that was exceeded.
60
+ *
61
+ * @param ms - the elapsed `connectTimeout`
62
+ */
63
+ constructor(ms) {
64
+ super(`Not connected within ${ms}ms (still retrying in background)`);
65
+ }
66
+ }
67
+ /** `sendTimeout` elapsed while queued, in flight, or awaiting an ack. */
68
+ export class WSTimeoutError extends WSError {
69
+ /**
70
+ * Reports the deadline that was exceeded.
71
+ *
72
+ * @param ms - the elapsed `sendTimeout`
73
+ */
74
+ constructor(ms) {
75
+ super(`No acknowledgement within ${ms}ms`);
76
+ }
77
+ }
78
+ /** The frame was evicted from a full outbox before it could be sent. */
79
+ export class WSOutboxDropError extends WSError {
80
+ constructor() {
81
+ super("Dropped from outbox (capacity reached while disconnected)");
82
+ }
83
+ }
84
+ /** The server rejected the operation with a `nack`. */
85
+ export class WSRemoteError extends WSError {
86
+ /** Machine-readable code from the server — see `ERROR_CODE`. */
87
+ code;
88
+ /**
89
+ * Lifts a wire-level error detail into a throwable.
90
+ *
91
+ * @param info - the error detail carried by the `nack` frame
92
+ */
93
+ constructor(info) {
94
+ super(info.message);
95
+ this.code = info.code;
96
+ }
97
+ }
98
+ /** The client was disposed while the operation was pending. */
99
+ export class WSDisposedError extends WSError {
100
+ constructor() {
101
+ super("Client disposed");
102
+ }
103
+ }
104
+ /** Sending was attempted while disconnected with buffering disabled. */
105
+ export class WSNotConnectedError extends WSError {
106
+ constructor() {
107
+ super("Not connected (outbox buffering is disabled)");
108
+ }
109
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Wire frame definitions.
3
+ *
4
+ * The central rule of this protocol: **the protocol frame type and the
5
+ * application message type are separate things.** The protocol owns a small
6
+ * closed set of frame types; `payload` is opaque and belongs entirely to the
7
+ * application. Your payload may contain its own `type` field and nothing will
8
+ * collide.
9
+ *
10
+ * @module
11
+ */
12
+ import type { FRAME, PRESENCE } from "./constants.js";
13
+ /** Structured error detail carried by `nack` and `error` frames. */
14
+ export interface WSErrorInfo {
15
+ /** Machine-readable code — see `ERROR_CODE`. */
16
+ code: string;
17
+ /** Human-readable explanation. Never parse this. */
18
+ message: string;
19
+ }
20
+ /**
21
+ * A message as delivered to application code.
22
+ *
23
+ * A `msg` frame minus its `type` field *is* a `WSMessage` — there is no
24
+ * translation layer and no divergence between wire names and API names.
25
+ */
26
+ export interface WSMessage<T = unknown> {
27
+ /** Room the message was published to. */
28
+ room: string;
29
+ /** Namespace the message belongs to. */
30
+ namespace: string;
31
+ /**
32
+ * Originating client id, or `null` when the message was injected
33
+ * server-side (via the service API or the HTTP routes).
34
+ */
35
+ from: string | null;
36
+ /** Opaque application payload. */
37
+ payload: T;
38
+ /** Server-assigned epoch milliseconds. */
39
+ timestamp: number;
40
+ }
41
+ /** Kind of presence change. */
42
+ export type PresenceEventType = (typeof PRESENCE)[keyof typeof PRESENCE];
43
+ /**
44
+ * A membership change in a room the client subscribed to with presence
45
+ * enabled.
46
+ *
47
+ * `sync` carries the full snapshot and is emitted on every (re)subscribe —
48
+ * crucially including after a reconnect, when membership may have changed
49
+ * completely while the client was away.
50
+ */
51
+ export interface WSPresenceEvent {
52
+ /** Whether this is a full snapshot or a join/leave delta. */
53
+ event: PresenceEventType;
54
+ /** Room the membership change happened in. */
55
+ room: string;
56
+ /** Namespace the room belongs to. */
57
+ namespace: string;
58
+ /** The client that joined or left. `null` for `sync`. */
59
+ clientId: string | null;
60
+ /** Full membership after applying this event. */
61
+ members: string[];
62
+ /** Server-assigned epoch milliseconds. */
63
+ timestamp: number;
64
+ }
65
+ /** A single room subscription request. */
66
+ export interface SubRequest {
67
+ /** Room name to join. */
68
+ room: string;
69
+ /** Track membership and deliver `presence` frames for this room. */
70
+ presence?: boolean;
71
+ }
72
+ /** Frames sent by the client. */
73
+ export type ClientFrame = {
74
+ type: typeof FRAME.AUTH;
75
+ id: string;
76
+ protocol: number;
77
+ payload: unknown;
78
+ /** Preferred client id — the server may override it. */
79
+ clientId?: string;
80
+ namespace?: string;
81
+ } | {
82
+ type: typeof FRAME.SUB;
83
+ id: string;
84
+ rooms: SubRequest[];
85
+ } | {
86
+ type: typeof FRAME.UNSUB;
87
+ id: string;
88
+ rooms: string[];
89
+ } | {
90
+ type: typeof FRAME.PUB;
91
+ id: string;
92
+ room: string;
93
+ namespace?: string;
94
+ payload: unknown;
95
+ } | {
96
+ type: typeof FRAME.BROADCAST;
97
+ id: string;
98
+ room: string;
99
+ payload: unknown;
100
+ } | {
101
+ type: typeof FRAME.PING;
102
+ };
103
+ /** Frames sent by the server. */
104
+ export type ServerFrame = {
105
+ type: typeof FRAME.HELLO;
106
+ clientId: string;
107
+ namespace: string;
108
+ protocol: number;
109
+ } | {
110
+ type: typeof FRAME.ACK;
111
+ id: string;
112
+ recipients?: number;
113
+ } | {
114
+ type: typeof FRAME.NACK;
115
+ id: string;
116
+ error: WSErrorInfo;
117
+ } | ({
118
+ type: typeof FRAME.MSG;
119
+ } & WSMessage) | ({
120
+ type: typeof FRAME.PRESENCE;
121
+ } & WSPresenceEvent) | {
122
+ type: typeof FRAME.PONG;
123
+ } | {
124
+ type: typeof FRAME.ERROR;
125
+ error: WSErrorInfo;
126
+ };
127
+ /** Any frame, in either direction. */
128
+ export type WSFrame = ClientFrame | ServerFrame;
129
+ /** Result of a successful `publish()` / `broadcast()`. */
130
+ export interface WSPublishResult {
131
+ /**
132
+ * Sockets the message was handed to **on the receiving server instance**.
133
+ *
134
+ * Best-effort telemetry, never a delivery guarantee — and it stays
135
+ * instance-local once a distributed adapter is in play.
136
+ */
137
+ recipients: number;
138
+ }
139
+ /** Outcome of the server's `verify()` hook. */
140
+ export interface AuthResult {
141
+ /** Assign a specific client id. Defaults to a generated one. */
142
+ clientId?: string;
143
+ /** Force the connection's namespace, overriding the client's request. */
144
+ namespace?: string;
145
+ /** Arbitrary data to associate with the connection (available to hooks). */
146
+ meta?: Record<string, unknown>;
147
+ }
148
+ /** Encodes an outgoing frame for the wire. */
149
+ export type WSEncoder = (frame: WSFrame) => string | ArrayBufferView | ArrayBuffer;
150
+ /** Decodes an inbound wire message into a frame. */
151
+ export type WSDecoder = (raw: string | ArrayBuffer) => WSFrame;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Wire frame definitions.
3
+ *
4
+ * The central rule of this protocol: **the protocol frame type and the
5
+ * application message type are separate things.** The protocol owns a small
6
+ * closed set of frame types; `payload` is opaque and belongs entirely to the
7
+ * application. Your payload may contain its own `type` field and nothing will
8
+ * collide.
9
+ *
10
+ * @module
11
+ */
12
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dependency-free wire protocol definitions, imported by both the client and
3
+ * the server. Keeping them in one place is what prevents the two sides from
4
+ * drifting apart.
5
+ *
6
+ * @module
7
+ */
8
+ export * from "./constants.js";
9
+ export * from "./errors.js";
10
+ export * from "./frames.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dependency-free wire protocol definitions, imported by both the client and
3
+ * the server. Keeping them in one place is what prevents the two sides from
4
+ * drifting apart.
5
+ *
6
+ * @module
7
+ */
8
+ export * from "./constants.js";
9
+ export * from "./errors.js";
10
+ export * from "./frames.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Entry point for `@marianmeres/ws/protocol`.
3
+ *
4
+ * The dependency-free wire definitions, for anyone implementing this protocol
5
+ * against a different server or client.
6
+ *
7
+ * @module
8
+ */
9
+ export * from "./protocol/mod.js";