@debros/orama 0.122.4-nightly

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +665 -0
  3. package/dist/index.d.ts +1334 -0
  4. package/dist/index.js +2553 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +82 -0
  7. package/src/auth/client.ts +276 -0
  8. package/src/auth/index.ts +3 -0
  9. package/src/auth/types.ts +62 -0
  10. package/src/cache/client.ts +203 -0
  11. package/src/cache/index.ts +14 -0
  12. package/src/core/http.ts +541 -0
  13. package/src/core/index.ts +10 -0
  14. package/src/core/interfaces/IAuthStrategy.ts +28 -0
  15. package/src/core/interfaces/IHttpTransport.ts +73 -0
  16. package/src/core/interfaces/IRetryPolicy.ts +20 -0
  17. package/src/core/interfaces/IWebSocketClient.ts +60 -0
  18. package/src/core/interfaces/index.ts +4 -0
  19. package/src/core/transport/AuthHeaderStrategy.ts +108 -0
  20. package/src/core/transport/RequestLogger.ts +116 -0
  21. package/src/core/transport/RequestRetryPolicy.ts +53 -0
  22. package/src/core/transport/TLSConfiguration.ts +53 -0
  23. package/src/core/transport/index.ts +4 -0
  24. package/src/core/ws.ts +246 -0
  25. package/src/db/client.ts +126 -0
  26. package/src/db/index.ts +13 -0
  27. package/src/db/qb.ts +111 -0
  28. package/src/db/repository.ts +128 -0
  29. package/src/db/types.ts +67 -0
  30. package/src/errors.ts +38 -0
  31. package/src/functions/client.ts +62 -0
  32. package/src/functions/index.ts +2 -0
  33. package/src/functions/types.ts +21 -0
  34. package/src/index.ts +201 -0
  35. package/src/network/client.ts +119 -0
  36. package/src/network/index.ts +7 -0
  37. package/src/pubsub/client.ts +361 -0
  38. package/src/pubsub/index.ts +12 -0
  39. package/src/pubsub/types.ts +46 -0
  40. package/src/storage/client.ts +272 -0
  41. package/src/storage/index.ts +7 -0
  42. package/src/utils/codec.ts +68 -0
  43. package/src/utils/index.ts +3 -0
  44. package/src/utils/platform.ts +44 -0
  45. package/src/utils/retry.ts +58 -0
  46. package/src/vault/auth.ts +98 -0
  47. package/src/vault/client.ts +197 -0
  48. package/src/vault/crypto/aes.ts +271 -0
  49. package/src/vault/crypto/hkdf.ts +42 -0
  50. package/src/vault/crypto/index.ts +27 -0
  51. package/src/vault/crypto/shamir.ts +173 -0
  52. package/src/vault/index.ts +65 -0
  53. package/src/vault/quorum.ts +16 -0
  54. package/src/vault/transport/fanout.ts +94 -0
  55. package/src/vault/transport/guardian.ts +285 -0
  56. package/src/vault/transport/index.ts +19 -0
  57. package/src/vault/transport/types.ts +101 -0
  58. package/src/vault/types.ts +62 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * WebSocket Client abstraction interface
3
+ * Provides a testable abstraction layer for WebSocket operations
4
+ */
5
+ export interface IWebSocketClient {
6
+ /**
7
+ * Connect to WebSocket server
8
+ */
9
+ connect(): Promise<void>;
10
+
11
+ /**
12
+ * Close WebSocket connection
13
+ */
14
+ close(): void;
15
+
16
+ /**
17
+ * Send data through WebSocket
18
+ */
19
+ send(data: string): void;
20
+
21
+ /**
22
+ * Register message handler
23
+ */
24
+ onMessage(handler: (data: string) => void): void;
25
+
26
+ /**
27
+ * Unregister message handler
28
+ */
29
+ offMessage(handler: (data: string) => void): void;
30
+
31
+ /**
32
+ * Register error handler
33
+ */
34
+ onError(handler: (error: Error) => void): void;
35
+
36
+ /**
37
+ * Unregister error handler
38
+ */
39
+ offError(handler: (error: Error) => void): void;
40
+
41
+ /**
42
+ * Register close handler
43
+ */
44
+ onClose(handler: (code: number, reason: string) => void): void;
45
+
46
+ /**
47
+ * Unregister close handler
48
+ */
49
+ offClose(handler: (code: number, reason: string) => void): void;
50
+
51
+ /**
52
+ * Check if WebSocket is connected
53
+ */
54
+ isConnected(): boolean;
55
+
56
+ /**
57
+ * Get WebSocket URL
58
+ */
59
+ get url(): string;
60
+ }
@@ -0,0 +1,4 @@
1
+ export type { IHttpTransport, RequestOptions } from "./IHttpTransport";
2
+ export type { IWebSocketClient } from "./IWebSocketClient";
3
+ export type { IAuthStrategy, RequestContext } from "./IAuthStrategy";
4
+ export type { IRetryPolicy } from "./IRetryPolicy";
@@ -0,0 +1,108 @@
1
+ import type { IAuthStrategy, RequestContext } from "../interfaces/IAuthStrategy";
2
+
3
+ /**
4
+ * Authentication type for different operations
5
+ */
6
+ type AuthType = "api-key-only" | "api-key-preferred" | "jwt-preferred" | "both";
7
+
8
+ /**
9
+ * Path-based authentication strategy
10
+ * Determines which auth credentials to use based on the request path
11
+ */
12
+ export class PathBasedAuthStrategy implements IAuthStrategy {
13
+ private apiKey?: string;
14
+ private jwt?: string;
15
+
16
+ /**
17
+ * Mapping of path patterns to auth types
18
+ */
19
+ private readonly authRules: Array<{ pattern: string; type: AuthType }> = [
20
+ // Database, PubSub, Proxy, Cache: prefer API key
21
+ { pattern: "/v1/rqlite/", type: "api-key-only" },
22
+ { pattern: "/v1/pubsub/", type: "api-key-only" },
23
+ { pattern: "/v1/proxy/", type: "api-key-only" },
24
+ { pattern: "/v1/cache/", type: "api-key-only" },
25
+ // Auth operations: prefer API key
26
+ { pattern: "/v1/auth/", type: "api-key-preferred" },
27
+ ];
28
+
29
+ constructor(apiKey?: string, jwt?: string) {
30
+ this.apiKey = apiKey;
31
+ this.jwt = jwt;
32
+ }
33
+
34
+ /**
35
+ * Get authentication headers for a request
36
+ */
37
+ getHeaders(context: RequestContext): Record<string, string> {
38
+ const headers: Record<string, string> = {};
39
+ const authType = this.detectAuthType(context.path);
40
+
41
+ switch (authType) {
42
+ case "api-key-only":
43
+ if (this.apiKey) {
44
+ headers["X-API-Key"] = this.apiKey;
45
+ } else if (this.jwt) {
46
+ // Fallback to JWT if no API key
47
+ headers["Authorization"] = `Bearer ${this.jwt}`;
48
+ }
49
+ break;
50
+
51
+ case "api-key-preferred":
52
+ if (this.apiKey) {
53
+ headers["X-API-Key"] = this.apiKey;
54
+ }
55
+ if (this.jwt) {
56
+ headers["Authorization"] = `Bearer ${this.jwt}`;
57
+ }
58
+ break;
59
+
60
+ case "jwt-preferred":
61
+ if (this.jwt) {
62
+ headers["Authorization"] = `Bearer ${this.jwt}`;
63
+ }
64
+ if (this.apiKey) {
65
+ headers["X-API-Key"] = this.apiKey;
66
+ }
67
+ break;
68
+
69
+ case "both":
70
+ if (this.jwt) {
71
+ headers["Authorization"] = `Bearer ${this.jwt}`;
72
+ }
73
+ if (this.apiKey) {
74
+ headers["X-API-Key"] = this.apiKey;
75
+ }
76
+ break;
77
+ }
78
+
79
+ return headers;
80
+ }
81
+
82
+ /**
83
+ * Set API key
84
+ */
85
+ setApiKey(apiKey?: string): void {
86
+ this.apiKey = apiKey;
87
+ }
88
+
89
+ /**
90
+ * Set JWT token
91
+ */
92
+ setJwt(jwt?: string): void {
93
+ this.jwt = jwt;
94
+ }
95
+
96
+ /**
97
+ * Detect auth type based on path
98
+ */
99
+ private detectAuthType(path: string): AuthType {
100
+ for (const rule of this.authRules) {
101
+ if (path.includes(rule.pattern)) {
102
+ return rule.type;
103
+ }
104
+ }
105
+ // Default: send both if available
106
+ return "both";
107
+ }
108
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Request logger for debugging HTTP operations
3
+ */
4
+ export class RequestLogger {
5
+ private readonly debug: boolean;
6
+
7
+ constructor(debug: boolean = false) {
8
+ this.debug = debug;
9
+ }
10
+
11
+ /**
12
+ * Log successful request
13
+ */
14
+ logSuccess(
15
+ method: string,
16
+ path: string,
17
+ duration: number,
18
+ queryDetails?: string
19
+ ): void {
20
+ if (typeof console === "undefined") return;
21
+
22
+ const logMessage = `[HttpClient] ${method} ${path} completed in ${duration.toFixed(2)}ms`;
23
+
24
+ if (queryDetails && this.debug) {
25
+ console.log(logMessage);
26
+ console.log(`[HttpClient] ${queryDetails}`);
27
+ } else {
28
+ console.log(logMessage);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Log failed request
34
+ */
35
+ logError(
36
+ method: string,
37
+ path: string,
38
+ duration: number,
39
+ error: any,
40
+ queryDetails?: string
41
+ ): void {
42
+ if (typeof console === "undefined") return;
43
+
44
+ // Special handling for 404 on find-one (expected behavior)
45
+ const is404FindOne =
46
+ path === "/v1/rqlite/find-one" &&
47
+ error?.httpStatus === 404;
48
+
49
+ if (is404FindOne) {
50
+ console.warn(
51
+ `[HttpClient] ${method} ${path} returned 404 after ${duration.toFixed(2)}ms (expected for optional lookups)`
52
+ );
53
+ return;
54
+ }
55
+
56
+ const errorMessage = `[HttpClient] ${method} ${path} failed after ${duration.toFixed(2)}ms:`;
57
+ console.error(errorMessage, error);
58
+
59
+ if (queryDetails && this.debug) {
60
+ console.error(`[HttpClient] ${queryDetails}`);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Extract query details from request for logging
66
+ */
67
+ extractQueryDetails(path: string, body?: any): string | null {
68
+ if (!this.debug) return null;
69
+
70
+ const isRqliteOperation = path.includes("/v1/rqlite/");
71
+ if (!isRqliteOperation || !body) return null;
72
+
73
+ try {
74
+ const parsedBody = typeof body === "string" ? JSON.parse(body) : body;
75
+
76
+ // Direct SQL query
77
+ if (parsedBody.sql) {
78
+ let details = `SQL: ${parsedBody.sql}`;
79
+ if (parsedBody.args && parsedBody.args.length > 0) {
80
+ details += ` | Args: [${parsedBody.args
81
+ .map((a: any) => (typeof a === "string" ? `"${a}"` : a))
82
+ .join(", ")}]`;
83
+ }
84
+ return details;
85
+ }
86
+
87
+ // Table-based query
88
+ if (parsedBody.table) {
89
+ let details = `Table: ${parsedBody.table}`;
90
+ if (parsedBody.criteria && Object.keys(parsedBody.criteria).length > 0) {
91
+ details += ` | Criteria: ${JSON.stringify(parsedBody.criteria)}`;
92
+ }
93
+ if (parsedBody.options) {
94
+ details += ` | Options: ${JSON.stringify(parsedBody.options)}`;
95
+ }
96
+ if (parsedBody.select) {
97
+ details += ` | Select: ${JSON.stringify(parsedBody.select)}`;
98
+ }
99
+ if (parsedBody.where) {
100
+ details += ` | Where: ${JSON.stringify(parsedBody.where)}`;
101
+ }
102
+ if (parsedBody.limit) {
103
+ details += ` | Limit: ${parsedBody.limit}`;
104
+ }
105
+ if (parsedBody.offset) {
106
+ details += ` | Offset: ${parsedBody.offset}`;
107
+ }
108
+ return details;
109
+ }
110
+ } catch {
111
+ // Failed to parse, ignore
112
+ }
113
+
114
+ return null;
115
+ }
116
+ }
@@ -0,0 +1,53 @@
1
+ import type { IRetryPolicy } from "../interfaces/IRetryPolicy";
2
+ import { SDKError } from "../../errors";
3
+
4
+ /**
5
+ * Exponential backoff retry policy
6
+ * Retries failed requests with increasing delays
7
+ */
8
+ export class ExponentialBackoffRetryPolicy implements IRetryPolicy {
9
+ private readonly maxRetries: number;
10
+ private readonly baseDelayMs: number;
11
+
12
+ /**
13
+ * HTTP status codes that should trigger a retry
14
+ */
15
+ private readonly retryableStatusCodes = [408, 429, 500, 502, 503, 504];
16
+
17
+ constructor(maxRetries: number = 3, baseDelayMs: number = 1000) {
18
+ this.maxRetries = maxRetries;
19
+ this.baseDelayMs = baseDelayMs;
20
+ }
21
+
22
+ /**
23
+ * Determine if request should be retried
24
+ */
25
+ shouldRetry(error: any, attempt: number): boolean {
26
+ // Don't retry if max attempts reached
27
+ if (attempt >= this.maxRetries) {
28
+ return false;
29
+ }
30
+
31
+ // Retry on retryable HTTP errors
32
+ if (error instanceof SDKError) {
33
+ return this.retryableStatusCodes.includes(error.httpStatus);
34
+ }
35
+
36
+ // Don't retry other errors
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Get delay before next retry (exponential backoff)
42
+ */
43
+ getDelay(attempt: number): number {
44
+ return this.baseDelayMs * (attempt + 1);
45
+ }
46
+
47
+ /**
48
+ * Get maximum number of retry attempts
49
+ */
50
+ getMaxRetries(): number {
51
+ return this.maxRetries;
52
+ }
53
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TLS Configuration for development/staging environments
3
+ *
4
+ * WARNING: Only use this in development/testing environments!
5
+ * DO NOT disable certificate validation in production.
6
+ */
7
+ export class TLSConfiguration {
8
+ /**
9
+ * Create fetch function with proper TLS configuration
10
+ */
11
+ static createFetchWithTLSConfig(): typeof fetch {
12
+ // Only allow insecure TLS in development
13
+ if (this.shouldAllowInsecure()) {
14
+ this.configureInsecureTLS();
15
+ }
16
+
17
+ return globalThis.fetch;
18
+ }
19
+
20
+ /**
21
+ * Check if insecure TLS should be allowed
22
+ */
23
+ private static shouldAllowInsecure(): boolean {
24
+ // Check if we're in Node.js environment
25
+ if (typeof process === "undefined" || !process.versions?.node) {
26
+ return false;
27
+ }
28
+
29
+ // Only allow in non-production with explicit flag
30
+ const isProduction = process.env.NODE_ENV === "production";
31
+ const allowInsecure = process.env.DEBROS_ALLOW_INSECURE_TLS === "true";
32
+
33
+ return !isProduction && allowInsecure;
34
+ }
35
+
36
+ /**
37
+ * Configure Node.js to allow insecure TLS
38
+ * WARNING: Only call in development!
39
+ */
40
+ private static configureInsecureTLS(): void {
41
+ if (typeof process !== "undefined" && process.env) {
42
+ // Allow self-signed/staging certificates for development
43
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
44
+
45
+ if (typeof console !== "undefined") {
46
+ console.warn(
47
+ "[TLSConfiguration] WARNING: TLS certificate validation disabled for development. " +
48
+ "DO NOT use in production!"
49
+ );
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,4 @@
1
+ export { PathBasedAuthStrategy } from "./AuthHeaderStrategy";
2
+ export { ExponentialBackoffRetryPolicy } from "./RequestRetryPolicy";
3
+ export { RequestLogger } from "./RequestLogger";
4
+ export { TLSConfiguration } from "./TLSConfiguration";
package/src/core/ws.ts ADDED
@@ -0,0 +1,246 @@
1
+ import WebSocket from "isomorphic-ws";
2
+ import { SDKError } from "../errors";
3
+ import { NetworkErrorCallback } from "./http";
4
+
5
+ export interface WSClientConfig {
6
+ wsURL: string;
7
+ timeout?: number;
8
+ authToken?: string;
9
+ WebSocket?: typeof WebSocket;
10
+ /**
11
+ * Callback invoked on WebSocket errors.
12
+ * Use this to trigger gateway failover at the application layer.
13
+ */
14
+ onNetworkError?: NetworkErrorCallback;
15
+ }
16
+
17
+ export type WSMessageHandler = (data: string) => void;
18
+ export type WSErrorHandler = (error: Error) => void;
19
+ export type WSCloseHandler = (code: number, reason: string) => void;
20
+ export type WSOpenHandler = () => void;
21
+
22
+ /**
23
+ * Simple WebSocket client with minimal abstractions
24
+ * No complex reconnection, no failover - keep it simple
25
+ * Gateway failover is handled at the application layer
26
+ */
27
+ export class WSClient {
28
+ private wsURL: string;
29
+ private timeout: number;
30
+ private authToken?: string;
31
+ private WebSocketClass: typeof WebSocket;
32
+ private onNetworkError?: NetworkErrorCallback;
33
+
34
+ private ws?: WebSocket;
35
+ private messageHandlers: Set<WSMessageHandler> = new Set();
36
+ private errorHandlers: Set<WSErrorHandler> = new Set();
37
+ private closeHandlers: Set<WSCloseHandler> = new Set();
38
+ private openHandlers: Set<WSOpenHandler> = new Set();
39
+ private isClosed = false;
40
+
41
+ constructor(config: WSClientConfig) {
42
+ this.wsURL = config.wsURL;
43
+ this.timeout = config.timeout ?? 30000;
44
+ this.authToken = config.authToken;
45
+ this.WebSocketClass = config.WebSocket ?? WebSocket;
46
+ this.onNetworkError = config.onNetworkError;
47
+ }
48
+
49
+ /**
50
+ * Set the network error callback
51
+ */
52
+ setOnNetworkError(callback: NetworkErrorCallback | undefined): void {
53
+ this.onNetworkError = callback;
54
+ }
55
+
56
+ /**
57
+ * Get the current WebSocket URL
58
+ */
59
+ get url(): string {
60
+ return this.wsURL;
61
+ }
62
+
63
+ /**
64
+ * Connect to WebSocket server
65
+ */
66
+ connect(): Promise<void> {
67
+ return new Promise((resolve, reject) => {
68
+ try {
69
+ const wsUrl = this.buildWSUrl();
70
+ this.ws = new this.WebSocketClass(wsUrl);
71
+ this.isClosed = false;
72
+
73
+ const timeout = setTimeout(() => {
74
+ this.ws?.close();
75
+ const error = new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT");
76
+
77
+ // Call the network error callback if configured
78
+ if (this.onNetworkError) {
79
+ this.onNetworkError(error, {
80
+ method: "WS",
81
+ path: this.wsURL,
82
+ isRetry: false,
83
+ attempt: 0,
84
+ });
85
+ }
86
+
87
+ reject(error);
88
+ }, this.timeout);
89
+
90
+ this.ws.addEventListener("open", () => {
91
+ clearTimeout(timeout);
92
+ console.log("[WSClient] Connected to", this.wsURL);
93
+ this.openHandlers.forEach((handler) => handler());
94
+ resolve();
95
+ });
96
+
97
+ this.ws.addEventListener("message", (event: Event) => {
98
+ const msgEvent = event as MessageEvent;
99
+ this.messageHandlers.forEach((handler) => handler(msgEvent.data));
100
+ });
101
+
102
+ this.ws.addEventListener("error", (event: Event) => {
103
+ console.error("[WSClient] WebSocket error:", event);
104
+ clearTimeout(timeout);
105
+ // Extract useful details from the event — raw Event objects don't serialize
106
+ const details: Record<string, any> = { type: event.type };
107
+ if ("message" in event) {
108
+ details.message = (event as ErrorEvent).message;
109
+ }
110
+ const error = new SDKError("WebSocket error", 0, "WS_ERROR", details);
111
+
112
+ // Call the network error callback if configured
113
+ if (this.onNetworkError) {
114
+ this.onNetworkError(error, {
115
+ method: "WS",
116
+ path: this.wsURL,
117
+ isRetry: false,
118
+ attempt: 0,
119
+ });
120
+ }
121
+
122
+ this.errorHandlers.forEach((handler) => handler(error));
123
+ reject(error);
124
+ });
125
+
126
+ this.ws.addEventListener("close", (event: Event) => {
127
+ clearTimeout(timeout);
128
+ const closeEvent = event as CloseEvent;
129
+ const code = closeEvent.code ?? 1006;
130
+ const reason = closeEvent.reason ?? "";
131
+ console.log(`[WSClient] Connection closed (code: ${code}, reason: ${reason || "none"})`);
132
+ this.closeHandlers.forEach((handler) => handler(code, reason));
133
+ });
134
+ } catch (error) {
135
+ reject(error);
136
+ }
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Build WebSocket URL with auth token
142
+ */
143
+ private buildWSUrl(): string {
144
+ let url = this.wsURL;
145
+
146
+ if (this.authToken) {
147
+ const separator = url.includes("?") ? "&" : "?";
148
+ const paramName = this.authToken.startsWith("ak_") ? "api_key" : "token";
149
+ // API keys contain a colon (ak_xxx:namespace) that must not be percent-encoded
150
+ const encodedToken = this.authToken.startsWith("ak_")
151
+ ? this.authToken
152
+ : encodeURIComponent(this.authToken);
153
+ url += `${separator}${paramName}=${encodedToken}`;
154
+ }
155
+
156
+ return url;
157
+ }
158
+
159
+ /**
160
+ * Register message handler
161
+ */
162
+ onMessage(handler: WSMessageHandler): () => void {
163
+ this.messageHandlers.add(handler);
164
+ return () => this.messageHandlers.delete(handler);
165
+ }
166
+
167
+ /**
168
+ * Unregister message handler
169
+ */
170
+ offMessage(handler: WSMessageHandler): void {
171
+ this.messageHandlers.delete(handler);
172
+ }
173
+
174
+ /**
175
+ * Register error handler
176
+ */
177
+ onError(handler: WSErrorHandler): () => void {
178
+ this.errorHandlers.add(handler);
179
+ return () => this.errorHandlers.delete(handler);
180
+ }
181
+
182
+ /**
183
+ * Unregister error handler
184
+ */
185
+ offError(handler: WSErrorHandler): void {
186
+ this.errorHandlers.delete(handler);
187
+ }
188
+
189
+ /**
190
+ * Register close handler
191
+ */
192
+ onClose(handler: WSCloseHandler): () => void {
193
+ this.closeHandlers.add(handler);
194
+ return () => this.closeHandlers.delete(handler);
195
+ }
196
+
197
+ /**
198
+ * Unregister close handler
199
+ */
200
+ offClose(handler: WSCloseHandler): void {
201
+ this.closeHandlers.delete(handler);
202
+ }
203
+
204
+ /**
205
+ * Register open handler
206
+ */
207
+ onOpen(handler: WSOpenHandler): () => void {
208
+ this.openHandlers.add(handler);
209
+ return () => this.openHandlers.delete(handler);
210
+ }
211
+
212
+ /**
213
+ * Send data through WebSocket
214
+ */
215
+ send(data: string): void {
216
+ if (this.ws?.readyState !== WebSocket.OPEN) {
217
+ throw new SDKError("WebSocket is not connected", 0, "WS_NOT_CONNECTED");
218
+ }
219
+ this.ws.send(data);
220
+ }
221
+
222
+ /**
223
+ * Close WebSocket connection
224
+ */
225
+ close(): void {
226
+ if (this.isClosed) {
227
+ return;
228
+ }
229
+ this.isClosed = true;
230
+ this.ws?.close();
231
+ }
232
+
233
+ /**
234
+ * Check if WebSocket is connected
235
+ */
236
+ isConnected(): boolean {
237
+ return !this.isClosed && this.ws?.readyState === WebSocket.OPEN;
238
+ }
239
+
240
+ /**
241
+ * Update auth token
242
+ */
243
+ setAuthToken(token?: string): void {
244
+ this.authToken = token;
245
+ }
246
+ }