@grest-ts/websocket 0.0.5

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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/adapter/BrowserSocketAdapter.d.ts +18 -0
  3. package/dist/src/adapter/BrowserSocketAdapter.d.ts.map +1 -0
  4. package/dist/src/adapter/BrowserSocketAdapter.js +51 -0
  5. package/dist/src/adapter/BrowserSocketAdapter.js.map +1 -0
  6. package/dist/src/adapter/NodeSocketAdapter.d.ts +21 -0
  7. package/dist/src/adapter/NodeSocketAdapter.d.ts.map +1 -0
  8. package/dist/src/adapter/NodeSocketAdapter.js +52 -0
  9. package/dist/src/adapter/NodeSocketAdapter.js.map +1 -0
  10. package/dist/src/adapter/getDefaultAdapter.d.ts +5 -0
  11. package/dist/src/adapter/getDefaultAdapter.d.ts.map +1 -0
  12. package/dist/src/adapter/getDefaultAdapter.js +15 -0
  13. package/dist/src/adapter/getDefaultAdapter.js.map +1 -0
  14. package/dist/src/client/GGSocketClient.d.ts +10 -0
  15. package/dist/src/client/GGSocketClient.d.ts.map +1 -0
  16. package/dist/src/client/GGSocketClient.js +17 -0
  17. package/dist/src/client/GGSocketClient.js.map +1 -0
  18. package/dist/src/client/GGSocketPool.d.ts +63 -0
  19. package/dist/src/client/GGSocketPool.d.ts.map +1 -0
  20. package/dist/src/client/GGSocketPool.js +203 -0
  21. package/dist/src/client/GGSocketPool.js.map +1 -0
  22. package/dist/src/index-browser.d.ts +11 -0
  23. package/dist/src/index-browser.d.ts.map +1 -0
  24. package/dist/src/index-browser.js +17 -0
  25. package/dist/src/index-browser.js.map +1 -0
  26. package/dist/src/index-node.d.ts +14 -0
  27. package/dist/src/index-node.d.ts.map +1 -0
  28. package/dist/src/index-node.js +22 -0
  29. package/dist/src/index-node.js.map +1 -0
  30. package/dist/src/schema/GGWebSocketMiddleware.d.ts +40 -0
  31. package/dist/src/schema/GGWebSocketMiddleware.d.ts.map +1 -0
  32. package/dist/src/schema/GGWebSocketMiddleware.js +6 -0
  33. package/dist/src/schema/GGWebSocketMiddleware.js.map +1 -0
  34. package/dist/src/schema/GGWebSocketSchema.d.ts +31 -0
  35. package/dist/src/schema/GGWebSocketSchema.d.ts.map +1 -0
  36. package/dist/src/schema/GGWebSocketSchema.js +32 -0
  37. package/dist/src/schema/GGWebSocketSchema.js.map +1 -0
  38. package/dist/src/schema/webSocketSchema.d.ts +52 -0
  39. package/dist/src/schema/webSocketSchema.d.ts.map +1 -0
  40. package/dist/src/schema/webSocketSchema.js +62 -0
  41. package/dist/src/schema/webSocketSchema.js.map +1 -0
  42. package/dist/src/server/GGSocketServer.d.ts +34 -0
  43. package/dist/src/server/GGSocketServer.d.ts.map +1 -0
  44. package/dist/src/server/GGSocketServer.js +186 -0
  45. package/dist/src/server/GGSocketServer.js.map +1 -0
  46. package/dist/src/server/GGWebSocketMetrics.d.ts +43 -0
  47. package/dist/src/server/GGWebSocketMetrics.d.ts.map +1 -0
  48. package/dist/src/server/GGWebSocketMetrics.js +38 -0
  49. package/dist/src/server/GGWebSocketMetrics.js.map +1 -0
  50. package/dist/src/server/GGWebSocketSchema.startServer.d.ts +33 -0
  51. package/dist/src/server/GGWebSocketSchema.startServer.d.ts.map +1 -0
  52. package/dist/src/server/GGWebSocketSchema.startServer.js +80 -0
  53. package/dist/src/server/GGWebSocketSchema.startServer.js.map +1 -0
  54. package/dist/src/server/GG_WS_CONNECTION.d.ts +12 -0
  55. package/dist/src/server/GG_WS_CONNECTION.d.ts.map +1 -0
  56. package/dist/src/server/GG_WS_CONNECTION.js +8 -0
  57. package/dist/src/server/GG_WS_CONNECTION.js.map +1 -0
  58. package/dist/src/server/GG_WS_MESSAGE.d.ts +10 -0
  59. package/dist/src/server/GG_WS_MESSAGE.d.ts.map +1 -0
  60. package/dist/src/server/GG_WS_MESSAGE.js +7 -0
  61. package/dist/src/server/GG_WS_MESSAGE.js.map +1 -0
  62. package/dist/src/socket/GGSocket.d.ts +81 -0
  63. package/dist/src/socket/GGSocket.d.ts.map +1 -0
  64. package/dist/src/socket/GGSocket.js +315 -0
  65. package/dist/src/socket/GGSocket.js.map +1 -0
  66. package/dist/src/socket/SocketAdapter.d.ts +13 -0
  67. package/dist/src/socket/SocketAdapter.d.ts.map +1 -0
  68. package/dist/src/socket/SocketAdapter.js +2 -0
  69. package/dist/src/socket/SocketAdapter.js.map +1 -0
  70. package/dist/src/socket/SocketMessage.d.ts +47 -0
  71. package/dist/src/socket/SocketMessage.d.ts.map +1 -0
  72. package/dist/src/socket/SocketMessage.js +51 -0
  73. package/dist/src/socket/SocketMessage.js.map +1 -0
  74. package/dist/src/socket/WebSocketTypes.d.ts +18 -0
  75. package/dist/src/socket/WebSocketTypes.d.ts.map +1 -0
  76. package/dist/src/socket/WebSocketTypes.js +5 -0
  77. package/dist/src/socket/WebSocketTypes.js.map +1 -0
  78. package/dist/src/socket/utils/PendingRequestsMap.d.ts +32 -0
  79. package/dist/src/socket/utils/PendingRequestsMap.d.ts.map +1 -0
  80. package/dist/src/socket/utils/PendingRequestsMap.js +104 -0
  81. package/dist/src/socket/utils/PendingRequestsMap.js.map +1 -0
  82. package/dist/src/tsconfig.json +17 -0
  83. package/dist/testkit/client/GGSocketCall.d.ts +37 -0
  84. package/dist/testkit/client/GGSocketCall.d.ts.map +1 -0
  85. package/dist/testkit/client/GGSocketCall.js +44 -0
  86. package/dist/testkit/client/GGSocketCall.js.map +1 -0
  87. package/dist/testkit/client/GGWebSocketSchema.callOn.d.ts +70 -0
  88. package/dist/testkit/client/GGWebSocketSchema.callOn.d.ts.map +1 -0
  89. package/dist/testkit/client/GGWebSocketSchema.callOn.js +135 -0
  90. package/dist/testkit/client/GGWebSocketSchema.callOn.js.map +1 -0
  91. package/dist/testkit/index-testkit.d.ts +4 -0
  92. package/dist/testkit/index-testkit.d.ts.map +1 -0
  93. package/dist/testkit/index-testkit.js +3 -0
  94. package/dist/testkit/index-testkit.js.map +1 -0
  95. package/dist/tsconfig.publish.tsbuildinfo +1 -0
  96. package/package.json +74 -0
  97. package/src/adapter/BrowserSocketAdapter.ts +63 -0
  98. package/src/adapter/NodeSocketAdapter.ts +67 -0
  99. package/src/adapter/getDefaultAdapter.ts +14 -0
  100. package/src/client/GGSocketClient.ts +25 -0
  101. package/src/client/GGSocketPool.ts +244 -0
  102. package/src/index-browser.ts +21 -0
  103. package/src/index-node.ts +28 -0
  104. package/src/schema/GGWebSocketMiddleware.ts +43 -0
  105. package/src/schema/GGWebSocketSchema.ts +57 -0
  106. package/src/schema/webSocketSchema.ts +109 -0
  107. package/src/server/GGSocketServer.ts +217 -0
  108. package/src/server/GGWebSocketMetrics.ts +58 -0
  109. package/src/server/GGWebSocketSchema.startServer.ts +136 -0
  110. package/src/server/GG_WS_CONNECTION.ts +10 -0
  111. package/src/server/GG_WS_MESSAGE.ts +9 -0
  112. package/src/socket/GGSocket.ts +394 -0
  113. package/src/socket/SocketAdapter.ts +21 -0
  114. package/src/socket/SocketMessage.ts +97 -0
  115. package/src/socket/WebSocketTypes.ts +19 -0
  116. package/src/socket/utils/PendingRequestsMap.ts +128 -0
  117. package/src/tsconfig.json +17 -0
@@ -0,0 +1,244 @@
1
+ import {GGSocket} from '../socket/GGSocket';
2
+ import {GGWebSocketHandshakeContext, GGWebSocketMiddleware} from "../schema/GGWebSocketMiddleware";
3
+ import {SocketAdapter} from "../socket/SocketAdapter";
4
+ import {GG_WS_CONNECTION} from "../server/GG_WS_CONNECTION";
5
+ import {Message, MessageType} from "../socket/SocketMessage";
6
+ import {GGValidator, SERVER_ERROR} from "@grest-ts/schema";
7
+ import {withTimeout} from "@grest-ts/common";
8
+ import {GGContext} from "@grest-ts/context";
9
+ import {GG_TRACE} from "@grest-ts/trace";
10
+ import {getDefaultAdapter} from "../adapter/getDefaultAdapter";
11
+
12
+ export interface GGSocketPoolConfig<Query> {
13
+ domain: string,
14
+ path: string,
15
+ query?: Query
16
+ queryValidator?: GGValidator<Query>
17
+ middlewares?: readonly GGWebSocketMiddleware[]
18
+ }
19
+
20
+ /**
21
+ * Connection pool for WebSocket connections
22
+ * Reuses existing connections when the same URL + headers combination is requested
23
+ */
24
+ export class GGSocketPool {
25
+ private static sockets = new Map<string, GGSocket>();
26
+ private static pendingSockets = new Map<string, Promise<GGSocket>>();
27
+ private static adapter: any = null;
28
+ private static adapterPromise: Promise<any> | null = null;
29
+
30
+ /**
31
+ * Get the current number of active connections in the pool
32
+ */
33
+ public static get size(): number {
34
+ return this.sockets.size;
35
+ }
36
+
37
+ /**
38
+ * Get the current number of pending connections being established
39
+ */
40
+ public static get pendingSize(): number {
41
+ return this.pendingSockets.size;
42
+ }
43
+
44
+ /**
45
+ * Close all pooled connections gracefully.
46
+ * Waits for pending connections to establish before closing them.
47
+ * @param graceful - If true, calls teardown() on each socket allowing pending requests to complete.
48
+ * If false, calls close() for immediate termination.
49
+ */
50
+ public static async closeAll(graceful: boolean = true): Promise<void> {
51
+ // Wait for any pending connections to establish first
52
+ const pendingPromises = Array.from(this.pendingSockets.values());
53
+ if (pendingPromises.length > 0) {
54
+ await Promise.allSettled(pendingPromises);
55
+ }
56
+
57
+ // Close all active connections
58
+ const sockets = Array.from(this.sockets.values());
59
+ if (graceful) {
60
+ await Promise.allSettled(sockets.map(socket => socket.teardown()));
61
+ } else {
62
+ sockets.forEach(socket => socket.close());
63
+ }
64
+
65
+ // Clear the maps (onClose handlers should have already removed them,
66
+ // but clear explicitly to handle any edge cases)
67
+ this.sockets.clear();
68
+ this.pendingSockets.clear();
69
+ }
70
+
71
+ /**
72
+ * Clear all maps without closing connections.
73
+ * WARNING: This will cause memory leaks if connections are still active.
74
+ * Only use for testing purposes.
75
+ */
76
+ public static __clearForTesting(): void {
77
+ this.sockets.clear();
78
+ this.pendingSockets.clear();
79
+ this.adapter = null;
80
+ this.adapterPromise = null;
81
+ }
82
+
83
+ /**
84
+ * Remove a specific connection from the pool by its key.
85
+ * Does NOT close the socket - just removes it from the pool.
86
+ * @returns true if the connection was found and removed, false otherwise
87
+ */
88
+ public static removeFromPool(key: string): boolean {
89
+ return this.sockets.delete(key);
90
+ }
91
+
92
+ /**
93
+ * Get all connection keys currently in the pool (for debugging/monitoring)
94
+ */
95
+ public static getConnectionKeys(): string[] {
96
+ return Array.from(this.sockets.keys());
97
+ }
98
+
99
+ /**
100
+ * Ensure adapter is loaded (lazy initialization)
101
+ */
102
+ private static async ensureAdapter(): Promise<any> {
103
+ if (this.adapter) {
104
+ return this.adapter;
105
+ }
106
+
107
+ if (!this.adapterPromise) {
108
+ this.adapterPromise = getDefaultAdapter();
109
+ }
110
+
111
+ // Always await the promise to handle race conditions
112
+ this.adapter = await this.adapterPromise;
113
+ return this.adapter;
114
+ }
115
+
116
+ public static setAdapter(adapter: new(args: any, options?: any) => SocketAdapter) {
117
+ this.adapter = adapter;
118
+ this.adapterPromise = Promise.resolve(adapter);
119
+ }
120
+
121
+ /**
122
+ * Build headers from middlewares' updateHandshake()
123
+ */
124
+ private static buildHeaders(config: GGSocketPoolConfig<any>): Record<string, string> {
125
+ if (!config.middlewares) {
126
+ return {};
127
+ }
128
+
129
+ const handshakeContext: GGWebSocketHandshakeContext = {
130
+ headers: {},
131
+ queryArgs: (config.query as Record<string, string>) ?? {}
132
+ };
133
+
134
+ for (const middleware of config.middlewares) {
135
+ middleware.updateHandshake?.(handshakeContext);
136
+ }
137
+
138
+ return handshakeContext.headers;
139
+ }
140
+
141
+ static async getOrConnect<Query>(
142
+ config: GGSocketPoolConfig<Query>
143
+ ): Promise<GGSocket> {
144
+ // Build headers from middlewares
145
+ const headers = this.buildHeaders(config);
146
+
147
+ // Build full URL with query string if provided
148
+ let fullUrl = config.domain + config.path;
149
+ if (config.query) {
150
+ const queryEntries: [string, string][] = Object.entries(config.query).map(([key, value]) => [key, String(value)]);
151
+ fullUrl += '?' + new URLSearchParams(queryEntries).toString();
152
+ }
153
+
154
+ // Create connection key based on URL + headers
155
+ const headerKey = Object.entries(headers).sort().map(([k, v]) => `${k}=${v}`).join('&');
156
+ const key = fullUrl + "::" + headerKey;
157
+
158
+ // Check for existing connection first
159
+ if (this.sockets.has(key)) {
160
+ return this.sockets.get(key);
161
+ }
162
+ if (this.pendingSockets.has(key)) {
163
+ return this.pendingSockets.get(key);
164
+ }
165
+
166
+ // Create the connection promise BEFORE any async operations to prevent race conditions
167
+ // This ensures that concurrent calls will see the pending promise
168
+ const connectionPromise = (async () => {
169
+ // Ensure adapter is loaded (this is async but safely inside the promise)
170
+ const adapterClass = await this.ensureAdapter();
171
+
172
+ return new Promise<GGSocket>((resolve, reject) => {
173
+ const adapter = new adapterClass(fullUrl);
174
+ adapter.onOpen(async () => {
175
+ try {
176
+ const context = new GGContext("ws-client-connection");
177
+ await context.run(async () => {
178
+ GG_TRACE.init();
179
+ GG_WS_CONNECTION.set({
180
+ port: undefined,
181
+ path: config.domain
182
+ });
183
+
184
+ // Send handshake with headers
185
+ adapter.send(Message.create(MessageType.HANDSHAKE, "", "", headers));
186
+
187
+ // Wait for handshake response
188
+ await withTimeout(
189
+ new Promise<void>((handshakeResolve, handshakeReject) => {
190
+ const onMessage = (data: string) => {
191
+ const msg = Message.parse(data);
192
+ if (!msg) return;
193
+
194
+ if (msg.type === MessageType.HANDSHAKE_OK) {
195
+ adapter.offMessage(onMessage);
196
+ handshakeResolve();
197
+ } else if (msg.type === MessageType.HANDSHAKE_ERR) {
198
+ adapter.offMessage(onMessage);
199
+ handshakeReject(new SERVER_ERROR({
200
+ displayMessage: 'WebSocket handshake failed',
201
+ originalError: msg.data
202
+ }));
203
+ }
204
+ };
205
+ adapter.onMessage(onMessage);
206
+ }),
207
+ 5000,
208
+ 'Handshake timeout'
209
+ );
210
+ resolve(new GGSocket(adapter, {connectionContext: context}));
211
+ });
212
+ } catch (error) {
213
+ reject(error);
214
+ }
215
+ });
216
+ adapter.onError((error: Error) => {
217
+ reject(error);
218
+ });
219
+ });
220
+ })();
221
+
222
+ // Store the pending promise IMMEDIATELY (before awaiting)
223
+ this.pendingSockets.set(key, connectionPromise);
224
+
225
+ try {
226
+ const socket = await connectionPromise;
227
+
228
+ // Store the connection
229
+ this.sockets.set(key, socket);
230
+ this.pendingSockets.delete(key);
231
+
232
+ // Clean up on close
233
+ socket.onClose(() => {
234
+ this.sockets.delete(key);
235
+ });
236
+
237
+ return socket;
238
+ } catch (error) {
239
+ // Clean up failed connection attempt
240
+ this.pendingSockets.delete(key);
241
+ throw error;
242
+ }
243
+ }
244
+ }
@@ -0,0 +1,21 @@
1
+ // Type helpers
2
+ export * from "./socket/WebSocketTypes";
3
+
4
+ // Metrics
5
+ export * from "./server/GGWebSocketMetrics";
6
+
7
+ // Context
8
+ export * from "./server/GG_WS_CONNECTION";
9
+ export * from "./server/GG_WS_MESSAGE";
10
+
11
+ // Core
12
+ export * from "./socket/GGSocket";
13
+
14
+ // API Schema
15
+ export * from "./schema/webSocketSchema";
16
+ export * from "./schema/GGWebSocketSchema";
17
+ export * from "./schema/GGWebSocketMiddleware";
18
+
19
+ // Client
20
+ export * from "./client/GGSocketClient";
21
+ export * from "./client/GGSocketPool";
@@ -0,0 +1,28 @@
1
+ // Type helpers
2
+ export * from "./socket/WebSocketTypes";
3
+
4
+ // Metrics
5
+ export * from "./server/GGWebSocketMetrics";
6
+
7
+ // Context
8
+ export * from "./server/GG_WS_CONNECTION";
9
+ export * from "./server/GG_WS_MESSAGE";
10
+
11
+ // Core
12
+ export * from "./socket/GGSocket";
13
+
14
+ // API Schema
15
+ export * from "./schema/webSocketSchema";
16
+ export * from "./schema/GGWebSocketSchema";
17
+ export * from "./schema/GGWebSocketMiddleware";
18
+
19
+ // Server
20
+ export * from "./server/GGSocketServer";
21
+ export * from "./server/GGWebSocketSchema.startServer";
22
+
23
+ // Client
24
+ export * from "./client/GGSocketClient";
25
+ export * from "./client/GGSocketPool";
26
+
27
+ // Extensions
28
+ import "./server/GGWebSocketSchema.startServer";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * WebSocket-specific middleware interface.
3
+ * Independent from @grest-ts/http - WebSocket has its own middleware system.
4
+ */
5
+
6
+ /**
7
+ * Context available during WebSocket handshake.
8
+ */
9
+ export interface GGWebSocketHandshakeContext {
10
+ /**
11
+ * Headers from the handshake message (client-sent or server-received).
12
+ */
13
+ headers: Record<string, string>;
14
+ /**
15
+ * Query parameters from the WebSocket URL.
16
+ */
17
+ queryArgs: Record<string, string>;
18
+ }
19
+
20
+ /**
21
+ * WebSocket middleware interface for both client and server.
22
+ * Unlike HTTP middleware, WebSocket middleware works with handshake data.
23
+ */
24
+ export interface GGWebSocketMiddleware {
25
+ /**
26
+ * Client-side: Add headers to the handshake message before sending.
27
+ * Called by GGSocketPool when establishing a connection.
28
+ */
29
+ updateHandshake?(context: GGWebSocketHandshakeContext): void;
30
+
31
+ /**
32
+ * Server-side: Parse headers from the received handshake message.
33
+ * Called by GGSocketServer when receiving a connection.
34
+ */
35
+ parseHandshake?(context: GGWebSocketHandshakeContext): void;
36
+
37
+ /**
38
+ * Server-side: Async processing after parseHandshake.
39
+ * Can be used for authentication, authorization, etc.
40
+ * Throwing an error will reject the connection.
41
+ */
42
+ process?(): Promise<void>;
43
+ }
@@ -0,0 +1,57 @@
1
+ import {GGWebSocketMiddleware} from "./GGWebSocketMiddleware";
2
+ import {GGContractApiDefinition, GGContractClass} from "@grest-ts/schema";
3
+
4
+ /**
5
+ * WebSocket API Schema - pure data definition with typed context
6
+ *
7
+ * Type parameters:
8
+ * - TClientToServer: Client-facing type for clientToServer methods (returns GGPromise)
9
+ * - TServerToClient: Client-facing type for serverToClient methods (returns GGPromise)
10
+ * - TContext: Accumulated context type (parseHandshake return types)
11
+ * - TQuery: Query parameters on connect
12
+ * - TClientToServerImpl: Server implementation type for clientToServer handlers (returns Promise)
13
+ */
14
+ export class GGWebSocketSchema<
15
+ TClientToServer,
16
+ TServerToClient,
17
+ TContext = {},
18
+ TQuery = undefined,
19
+ TClientToServerImpl = TClientToServer
20
+ > {
21
+ public readonly name: string
22
+ public readonly path: string
23
+ public readonly middlewares: readonly GGWebSocketMiddleware[]
24
+ private readonly contractFactory: () => GGWebSocketContractRuntime
25
+ private contractCache: GGWebSocketContractRuntime | null = null
26
+
27
+ constructor(
28
+ name: string,
29
+ path: string,
30
+ contractFactory: () => GGWebSocketContractRuntime,
31
+ middlewares: readonly GGWebSocketMiddleware[] = []
32
+ ) {
33
+ this.name = name
34
+ this.path = path
35
+ this.middlewares = middlewares
36
+ this.contractFactory = contractFactory
37
+ Object.freeze(this.middlewares)
38
+ }
39
+
40
+ get contract(): GGWebSocketContractRuntime {
41
+ if (!this.contractCache) {
42
+ this.contractCache = this.contractFactory()
43
+ }
44
+ Object.freeze(this)
45
+ return this.contractCache
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Runtime contract structure for WebSocket APIs.
51
+ * Contains GGContractClass instances for proper validation via implement().
52
+ */
53
+ export interface GGWebSocketContractRuntime {
54
+ apiName: string
55
+ clientToServer: GGContractClass<GGContractApiDefinition>
56
+ serverToClient: GGContractClass<GGContractApiDefinition>
57
+ }
@@ -0,0 +1,109 @@
1
+ import {GGWebSocketSchema, GGWebSocketContractRuntime} from "./GGWebSocketSchema";
2
+ import {GGWebSocketMiddleware} from "./GGWebSocketMiddleware";
3
+ import {GGContractClass, GGContractClient, GGContractImplementation, GGContractMethod} from "@grest-ts/schema";
4
+
5
+ /**
6
+ * Bidirectional websocket contract methods
7
+ */
8
+ export interface GGSocketContractMethods {
9
+ clientToServer: Record<string, GGContractMethod>
10
+ serverToClient: Record<string, GGContractMethod>
11
+ }
12
+
13
+ /**
14
+ * WebSocket contract definition
15
+ */
16
+ export interface GGSocketContract<TDef extends GGSocketContractMethods = GGSocketContractMethods> {
17
+ name: string
18
+ methods: TDef
19
+ }
20
+
21
+ /**
22
+ * Create a websocket contract
23
+ */
24
+ export function defineSocketContract<TDef extends GGSocketContractMethods>(
25
+ name: string,
26
+ methods: TDef
27
+ ): GGSocketContract<TDef> {
28
+ return {name, methods}
29
+ }
30
+
31
+ /**
32
+ * Create a WebSocket API schema builder from a contract.
33
+ *
34
+ * @example
35
+ * export const ChatContract = defineSocketContract("Chat", {
36
+ * clientToServer: {
37
+ * sendMessage: { input: IsMessage, success: IsVoid, errors: [SERVER_ERROR] }
38
+ * },
39
+ * serverToClient: {
40
+ * onMessage: { input: IsMessage }
41
+ * }
42
+ * })
43
+ *
44
+ * export const ChatApi = webSocketSchema(ChatContract)
45
+ * .path("/chat")
46
+ * .use(AuthMiddleware)
47
+ * .done()
48
+ */
49
+ export function webSocketSchema<TDef extends GGSocketContractMethods>(
50
+ contract: GGSocketContract<TDef>
51
+ ): GGWebSocketSchemaBuilder<
52
+ GGContractClient<TDef["clientToServer"]>,
53
+ GGContractClient<TDef["serverToClient"]>,
54
+ undefined,
55
+ undefined,
56
+ GGContractImplementation<TDef["clientToServer"]>
57
+ > {
58
+ return new GGWebSocketSchemaBuilder(contract)
59
+ }
60
+
61
+ class GGWebSocketSchemaBuilder<
62
+ TClientToServer,
63
+ TServerToClient,
64
+ TContext = undefined,
65
+ TQuery = undefined,
66
+ TClientToServerImpl = TClientToServer
67
+ > {
68
+ private _path: string = ""
69
+ private _middlewares: GGWebSocketMiddleware[] = []
70
+
71
+ constructor(
72
+ private readonly _contract: GGSocketContract
73
+ ) {
74
+ }
75
+
76
+ path(path: string): this {
77
+ this._path = path
78
+ return this
79
+ }
80
+
81
+ use<M extends GGWebSocketMiddleware>(middleware: M): GGWebSocketSchemaBuilder<TClientToServer, TServerToClient, TContext | M, TQuery, TClientToServerImpl> {
82
+ this._middlewares.push(middleware)
83
+ return this as any
84
+ }
85
+
86
+ queryOnConnect<TNewQuery>(): GGWebSocketSchemaBuilder<TClientToServer, TServerToClient, TContext, TNewQuery, TClientToServerImpl> {
87
+ return this as any
88
+ }
89
+
90
+ done(): GGWebSocketSchema<TClientToServer, TServerToClient, TContext, TQuery, TClientToServerImpl> {
91
+ const contract = this._contract;
92
+ const contractFactory = (): GGWebSocketContractRuntime => {
93
+ const methods = contract.methods;
94
+ const name = contract.name;
95
+ return {
96
+ apiName: name,
97
+ clientToServer: new GGContractClass(name + ".clientToServer", methods.clientToServer),
98
+ serverToClient: new GGContractClass(name + ".serverToClient", methods.serverToClient)
99
+ };
100
+ };
101
+
102
+ return new GGWebSocketSchema<TClientToServer, TServerToClient, TContext, TQuery, TClientToServerImpl>(
103
+ contract.name,
104
+ this._path,
105
+ contractFactory,
106
+ this._middlewares
107
+ )
108
+ }
109
+ }