@grest-ts/websocket 0.0.6 → 0.0.8

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.
@@ -1,21 +1,21 @@
1
- export interface SocketAdapter {
2
- send(message: string): void;
3
-
4
- close(): void;
5
-
6
- onOpen(handler: () => void): void;
7
-
8
- onMessage(handler: (data: string) => void): void;
9
-
10
- onClose(handler: () => void): void;
11
-
12
- onError(handler: (error: Error) => void): void;
13
-
14
- offOpen(handler: () => void): void;
15
-
16
- offMessage(handler: (data: string) => void): void;
17
-
18
- offClose(handler: () => void): void;
19
-
20
- offError(handler: (error: Error) => void): void;
21
- }
1
+ export interface SocketAdapter {
2
+ send(message: string): void;
3
+
4
+ close(): void;
5
+
6
+ onOpen(handler: () => void): void;
7
+
8
+ onMessage(handler: (data: string) => void): void;
9
+
10
+ onClose(handler: () => void): void;
11
+
12
+ onError(handler: (error: Error) => void): void;
13
+
14
+ offOpen(handler: () => void): void;
15
+
16
+ offMessage(handler: (data: string) => void): void;
17
+
18
+ offClose(handler: () => void): void;
19
+
20
+ offError(handler: (error: Error) => void): void;
21
+ }
@@ -1,97 +1,97 @@
1
- import {tPendingMessageId} from "./utils/PendingRequestsMap";
2
- import {OK_JSON} from "@grest-ts/schema";
3
-
4
- export const DELIMITER = ":"
5
-
6
- // Message types (single character for fast type checking)
7
- export enum MessageType {
8
- HANDSHAKE = "h", // Handshake request (client -> server with headers)
9
- HANDSHAKE_OK = "k", // Handshake success (server -> client)
10
- HANDSHAKE_ERR = "x", // Handshake error (server -> client)
11
- MSG = "m", // Regular message (send-and-forget)
12
- REQ = "r", // Request (expects response)
13
- RES = "s", // Successful response
14
- }
15
-
16
- export interface SocketMessage {
17
- type: MessageType;
18
- path: string;
19
- data?: any;
20
- }
21
-
22
- export interface HandshakeMessage extends SocketMessage {
23
- type: MessageType.HANDSHAKE;
24
- data: Record<string, string>; // Headers
25
- }
26
-
27
- export interface HandshakeOkMessage extends SocketMessage {
28
- type: MessageType.HANDSHAKE_OK;
29
- }
30
-
31
- export interface HandshakeErrMessage extends SocketMessage {
32
- type: MessageType.HANDSHAKE_ERR;
33
- data: any; // Error details
34
- }
35
-
36
- export interface RegularMessage extends SocketMessage {
37
- type: MessageType.MSG;
38
- data?: any;
39
- }
40
-
41
- export interface RequestMessage extends SocketMessage {
42
- type: MessageType.REQ;
43
- id: tPendingMessageId;
44
- data?: any;
45
- }
46
-
47
- export interface ResponseMessage extends SocketMessage {
48
- type: MessageType.RES;
49
- id: tPendingMessageId;
50
- data: OK_JSON<any>
51
- }
52
-
53
- export type AnyMessage = HandshakeMessage | HandshakeOkMessage | HandshakeErrMessage | RegularMessage | RequestMessage | ResponseMessage;
54
-
55
- export class Message {
56
-
57
- public static create(type: MessageType, path: string, id: tPendingMessageId | "", data: any): string {
58
- const dataStr = data !== undefined ? JSON.stringify(data) : "";
59
- return type + DELIMITER + String(path) + DELIMITER + (id || "") + DELIMITER + dataStr;
60
- }
61
-
62
- public static parse(msg: unknown): AnyMessage | undefined {
63
- if (!msg) {
64
- return undefined;
65
- }
66
- const parts = String(msg).split(DELIMITER);
67
-
68
- // Extract the first 4 parts (type, path, id) and everything else is data
69
- const type = parts[0];
70
- const path = parts[1];
71
- const id = parts[2];
72
- const data = parts.length > 3 ? parts.slice(3).join(DELIMITER) : undefined;
73
-
74
- // Handshake messages don't require path
75
- const isHandshake = type === MessageType.HANDSHAKE ||
76
- type === MessageType.HANDSHAKE_OK ||
77
- type === MessageType.HANDSHAKE_ERR;
78
-
79
- if (!type || (!isHandshake && !path)) {
80
- return undefined;
81
- }
82
- let dataParsed: any = undefined;
83
- if (data) {
84
- try {
85
- dataParsed = JSON.parse(data);
86
- } catch (e) {
87
- // If JSON parse fails, keep as undefined
88
- }
89
- }
90
- return {
91
- type: type as MessageType,
92
- path,
93
- id: (id || undefined) as any,
94
- data: dataParsed
95
- } as AnyMessage;
96
- }
97
- }
1
+ import {tPendingMessageId} from "./utils/PendingRequestsMap";
2
+ import {OK_JSON} from "@grest-ts/schema";
3
+
4
+ export const DELIMITER = ":"
5
+
6
+ // Message types (single character for fast type checking)
7
+ export enum MessageType {
8
+ HANDSHAKE = "h", // Handshake request (client -> server with headers)
9
+ HANDSHAKE_OK = "k", // Handshake success (server -> client)
10
+ HANDSHAKE_ERR = "x", // Handshake error (server -> client)
11
+ MSG = "m", // Regular message (send-and-forget)
12
+ REQ = "r", // Request (expects response)
13
+ RES = "s", // Successful response
14
+ }
15
+
16
+ export interface SocketMessage {
17
+ type: MessageType;
18
+ path: string;
19
+ data?: any;
20
+ }
21
+
22
+ export interface HandshakeMessage extends SocketMessage {
23
+ type: MessageType.HANDSHAKE;
24
+ data: Record<string, string>; // Headers
25
+ }
26
+
27
+ export interface HandshakeOkMessage extends SocketMessage {
28
+ type: MessageType.HANDSHAKE_OK;
29
+ }
30
+
31
+ export interface HandshakeErrMessage extends SocketMessage {
32
+ type: MessageType.HANDSHAKE_ERR;
33
+ data: any; // Error details
34
+ }
35
+
36
+ export interface RegularMessage extends SocketMessage {
37
+ type: MessageType.MSG;
38
+ data?: any;
39
+ }
40
+
41
+ export interface RequestMessage extends SocketMessage {
42
+ type: MessageType.REQ;
43
+ id: tPendingMessageId;
44
+ data?: any;
45
+ }
46
+
47
+ export interface ResponseMessage extends SocketMessage {
48
+ type: MessageType.RES;
49
+ id: tPendingMessageId;
50
+ data: OK_JSON<any>
51
+ }
52
+
53
+ export type AnyMessage = HandshakeMessage | HandshakeOkMessage | HandshakeErrMessage | RegularMessage | RequestMessage | ResponseMessage;
54
+
55
+ export class Message {
56
+
57
+ public static create(type: MessageType, path: string, id: tPendingMessageId | "", data: any): string {
58
+ const dataStr = data !== undefined ? JSON.stringify(data) : "";
59
+ return type + DELIMITER + String(path) + DELIMITER + (id || "") + DELIMITER + dataStr;
60
+ }
61
+
62
+ public static parse(msg: unknown): AnyMessage | undefined {
63
+ if (!msg) {
64
+ return undefined;
65
+ }
66
+ const parts = String(msg).split(DELIMITER);
67
+
68
+ // Extract the first 4 parts (type, path, id) and everything else is data
69
+ const type = parts[0];
70
+ const path = parts[1];
71
+ const id = parts[2];
72
+ const data = parts.length > 3 ? parts.slice(3).join(DELIMITER) : undefined;
73
+
74
+ // Handshake messages don't require path
75
+ const isHandshake = type === MessageType.HANDSHAKE ||
76
+ type === MessageType.HANDSHAKE_OK ||
77
+ type === MessageType.HANDSHAKE_ERR;
78
+
79
+ if (!type || (!isHandshake && !path)) {
80
+ return undefined;
81
+ }
82
+ let dataParsed: any = undefined;
83
+ if (data) {
84
+ try {
85
+ dataParsed = JSON.parse(data);
86
+ } catch (e) {
87
+ // If JSON parse fails, keep as undefined
88
+ }
89
+ }
90
+ return {
91
+ type: type as MessageType,
92
+ path,
93
+ id: (id || undefined) as any,
94
+ data: dataParsed
95
+ } as AnyMessage;
96
+ }
97
+ }
@@ -1,19 +1,19 @@
1
- /**
2
- * Type helpers for WebSocket connection handlers
3
- */
4
-
5
- /**
6
- * Incoming handler for clientToServer messages.
7
- * Call .on() with handlers for each method.
8
- */
9
- export type WebSocketIncoming<TClientToServer> = {
10
- on(handlers: TClientToServer): void
11
- }
12
-
13
- /**
14
- * Outgoing interface for serverToClient messages.
15
- * Includes all serverToClient methods plus onClose for lifecycle.
16
- */
17
- export type WebSocketOutgoing<TServerToClient> = TServerToClient & {
18
- onClose(callback: () => void): void
19
- }
1
+ /**
2
+ * Type helpers for WebSocket connection handlers
3
+ */
4
+
5
+ /**
6
+ * Incoming handler for clientToServer messages.
7
+ * Call .on() with handlers for each method.
8
+ */
9
+ export type WebSocketIncoming<TClientToServer> = {
10
+ on(handlers: TClientToServer): void
11
+ }
12
+
13
+ /**
14
+ * Outgoing interface for serverToClient messages.
15
+ * Includes all serverToClient methods plus onClose for lifecycle.
16
+ */
17
+ export type WebSocketOutgoing<TServerToClient> = TServerToClient & {
18
+ onClose(callback: () => void): void
19
+ }
@@ -1,128 +1,128 @@
1
- import {ERROR_JSON, OK_JSON, SERVER_ERROR} from "@grest-ts/schema";
2
-
3
- interface PendingRequest {
4
- resolve: (value: any) => void;
5
- reject: (error: any) => void;
6
- timeout: any;
7
- path: string;
8
- }
9
-
10
- export type tPendingMessageId = string & { tt: never };
11
-
12
- export class PendingRequestsMap {
13
- private readonly requests: Map<tPendingMessageId, PendingRequest> = new Map();
14
- private requestIdCounter: number = 1;
15
- // Callbacks waiting for the map to drain (become empty)
16
- private drainCallbacks: Array<() => void> = [];
17
-
18
- /**
19
- * Get the number of pending requests
20
- */
21
- public get size(): number {
22
- return this.requests.size;
23
- }
24
-
25
- /**
26
- * Check if there are any pending requests
27
- */
28
- public hasPending(): boolean {
29
- return this.requests.size > 0;
30
- }
31
-
32
- /**
33
- * Wait for all pending requests to complete (resolve or reject).
34
- * Uses event-driven notification instead of polling.
35
- * @param timeoutMs - Maximum time to wait for pending requests
36
- * @returns Promise that resolves when all pending requests are done or timeout is reached
37
- */
38
- public waitForPending(timeoutMs: number = 5000): Promise<void> {
39
- if (this.requests.size === 0) {
40
- return Promise.resolve();
41
- }
42
-
43
- return new Promise<void>((resolve) => {
44
- const cleanup = () => {
45
- clearTimeout(timeout);
46
- const idx = this.drainCallbacks.indexOf(onDrain);
47
- if (idx !== -1) this.drainCallbacks.splice(idx, 1);
48
- };
49
-
50
- const onDrain = () => {
51
- cleanup();
52
- resolve();
53
- };
54
-
55
- const timeout = setTimeout(() => {
56
- cleanup();
57
- resolve(); // Resolve on timeout (caller checks hasPending)
58
- }, timeoutMs);
59
-
60
- this.drainCallbacks.push(onDrain);
61
- });
62
- }
63
-
64
- /**
65
- * Notify drain callbacks if map is empty
66
- */
67
- private notifyDrainIfEmpty(): void {
68
- if (this.requests.size === 0 && this.drainCallbacks.length > 0) {
69
- const callbacks = [...this.drainCallbacks];
70
- this.drainCallbacks = [];
71
- callbacks.forEach(cb => cb());
72
- }
73
- }
74
-
75
- public create(
76
- path: string,
77
- timeoutMs: number,
78
- callback: (id: tPendingMessageId, waitForResponsePromise: Promise<any>) => Promise<any>
79
- ): Promise<any> {
80
- const requestId = String(this.requestIdCounter++) as tPendingMessageId;
81
- const reqPromise = new Promise((resolve, reject) => {
82
- const timeout = setTimeout(() => {
83
- this.requests.delete(requestId);
84
- this.notifyDrainIfEmpty();
85
- reject(new SERVER_ERROR({
86
- displayMessage: 'Request timeout',
87
- debugData: {
88
- timeout: timeoutMs,
89
- }
90
- }));
91
- }, timeoutMs);
92
- this.requests.set(requestId, {resolve, reject, timeout, path});
93
- });
94
-
95
- return callback(requestId, reqPromise)
96
- .catch((error) => {
97
- // If callback throws an error, clean up the pending request
98
- const pending = this.requests.get(requestId);
99
- if (pending) {
100
- clearTimeout(pending.timeout);
101
- this.requests.delete(requestId);
102
- this.notifyDrainIfEmpty();
103
- }
104
- throw error;
105
- });
106
- }
107
-
108
- public resolve(requestId: tPendingMessageId, value: OK_JSON<any> | ERROR_JSON): boolean {
109
- const pending = this.requests.get(requestId);
110
- if (!pending) {
111
- throw new Error('Pending request not found: ' + requestId);
112
- }
113
- clearTimeout(pending.timeout);
114
- this.requests.delete(requestId);
115
- pending.resolve(value);
116
- this.notifyDrainIfEmpty();
117
- return true;
118
- }
119
-
120
- public rejectAll(error: typeof SERVER_ERROR.infer): void {
121
- this.requests.forEach((pending) => {
122
- clearTimeout(pending.timeout);
123
- pending.resolve(error.toJSON());
124
- });
125
- this.requests.clear();
126
- this.notifyDrainIfEmpty();
127
- }
128
- }
1
+ import {ERROR_JSON, OK_JSON, SERVER_ERROR} from "@grest-ts/schema";
2
+
3
+ interface PendingRequest {
4
+ resolve: (value: any) => void;
5
+ reject: (error: any) => void;
6
+ timeout: any;
7
+ path: string;
8
+ }
9
+
10
+ export type tPendingMessageId = string & { tt: never };
11
+
12
+ export class PendingRequestsMap {
13
+ private readonly requests: Map<tPendingMessageId, PendingRequest> = new Map();
14
+ private requestIdCounter: number = 1;
15
+ // Callbacks waiting for the map to drain (become empty)
16
+ private drainCallbacks: Array<() => void> = [];
17
+
18
+ /**
19
+ * Get the number of pending requests
20
+ */
21
+ public get size(): number {
22
+ return this.requests.size;
23
+ }
24
+
25
+ /**
26
+ * Check if there are any pending requests
27
+ */
28
+ public hasPending(): boolean {
29
+ return this.requests.size > 0;
30
+ }
31
+
32
+ /**
33
+ * Wait for all pending requests to complete (resolve or reject).
34
+ * Uses event-driven notification instead of polling.
35
+ * @param timeoutMs - Maximum time to wait for pending requests
36
+ * @returns Promise that resolves when all pending requests are done or timeout is reached
37
+ */
38
+ public waitForPending(timeoutMs: number = 5000): Promise<void> {
39
+ if (this.requests.size === 0) {
40
+ return Promise.resolve();
41
+ }
42
+
43
+ return new Promise<void>((resolve) => {
44
+ const cleanup = () => {
45
+ clearTimeout(timeout);
46
+ const idx = this.drainCallbacks.indexOf(onDrain);
47
+ if (idx !== -1) this.drainCallbacks.splice(idx, 1);
48
+ };
49
+
50
+ const onDrain = () => {
51
+ cleanup();
52
+ resolve();
53
+ };
54
+
55
+ const timeout = setTimeout(() => {
56
+ cleanup();
57
+ resolve(); // Resolve on timeout (caller checks hasPending)
58
+ }, timeoutMs);
59
+
60
+ this.drainCallbacks.push(onDrain);
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Notify drain callbacks if map is empty
66
+ */
67
+ private notifyDrainIfEmpty(): void {
68
+ if (this.requests.size === 0 && this.drainCallbacks.length > 0) {
69
+ const callbacks = [...this.drainCallbacks];
70
+ this.drainCallbacks = [];
71
+ callbacks.forEach(cb => cb());
72
+ }
73
+ }
74
+
75
+ public create(
76
+ path: string,
77
+ timeoutMs: number,
78
+ callback: (id: tPendingMessageId, waitForResponsePromise: Promise<any>) => Promise<any>
79
+ ): Promise<any> {
80
+ const requestId = String(this.requestIdCounter++) as tPendingMessageId;
81
+ const reqPromise = new Promise((resolve, reject) => {
82
+ const timeout = setTimeout(() => {
83
+ this.requests.delete(requestId);
84
+ this.notifyDrainIfEmpty();
85
+ reject(new SERVER_ERROR({
86
+ displayMessage: 'Request timeout',
87
+ debugData: {
88
+ timeout: timeoutMs,
89
+ }
90
+ }));
91
+ }, timeoutMs);
92
+ this.requests.set(requestId, {resolve, reject, timeout, path});
93
+ });
94
+
95
+ return callback(requestId, reqPromise)
96
+ .catch((error) => {
97
+ // If callback throws an error, clean up the pending request
98
+ const pending = this.requests.get(requestId);
99
+ if (pending) {
100
+ clearTimeout(pending.timeout);
101
+ this.requests.delete(requestId);
102
+ this.notifyDrainIfEmpty();
103
+ }
104
+ throw error;
105
+ });
106
+ }
107
+
108
+ public resolve(requestId: tPendingMessageId, value: OK_JSON<any> | ERROR_JSON): boolean {
109
+ const pending = this.requests.get(requestId);
110
+ if (!pending) {
111
+ throw new Error('Pending request not found: ' + requestId);
112
+ }
113
+ clearTimeout(pending.timeout);
114
+ this.requests.delete(requestId);
115
+ pending.resolve(value);
116
+ this.notifyDrainIfEmpty();
117
+ return true;
118
+ }
119
+
120
+ public rejectAll(error: typeof SERVER_ERROR.infer): void {
121
+ this.requests.forEach((pending) => {
122
+ clearTimeout(pending.timeout);
123
+ pending.resolve(error.toJSON());
124
+ });
125
+ this.requests.clear();
126
+ this.notifyDrainIfEmpty();
127
+ }
128
+ }