@nevuamarkets/poly-websockets 0.0.1

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 ADDED
@@ -0,0 +1,251 @@
1
+ # Poly-WebSockets
2
+
3
+ A TypeScript library for real-time Polymarket WebSocket price alerts with automatic connection management and intelligent reconnection handling.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install poly-websockets
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - 🔄 **Automatic Connection Management**: Handles WebSocket connections, reconnections, and cleanup for grouped subscriptions
14
+ - 📊 **Real-time Price Updates**: Get live price data, order book updates, and trade events from Polymarket
15
+ - 🎯 **Smart Price Logic**: Implements Polymarket's price calculation logic (midpoint vs last trade price based on spread)
16
+ - 🚦 **Rate Limiting**: Built-in rate limiting to respect Polymarket API limits
17
+ - 🔗 **Group Management**: Efficiently manages multiple asset subscriptions across connection groups
18
+ - 💪 **TypeScript Support**: Full TypeScript definitions for all events and handlers
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { WSSubscriptionManager, WebSocketHandlers } from '@nevuamarkets/poly-websockets';
24
+
25
+ // Define your event handlers
26
+ const handlers: WebSocketHandlers = {
27
+ onPolymarketPriceUpdate: async (events) => {
28
+ events.forEach(event => {
29
+ console.log(`Price update for ${event.asset_id}: $${event.price}`);
30
+ });
31
+ },
32
+ onError: async (error) => {
33
+ console.error('WebSocket error:', error);
34
+ }
35
+ };
36
+
37
+ // Create the subscription manager
38
+ const manager = new WSSubscriptionManager(handlers);
39
+
40
+ // Subscribe to assets
41
+ await manager.addSubscriptions(['asset-id-1', 'asset-id-2']);
42
+
43
+ // Remove subscriptions
44
+ await manager.removeSubscriptions(['asset-id-1']);
45
+
46
+ // Clear all subscriptions and connections
47
+ await manager.clearState();
48
+ ```
49
+
50
+ ## API Reference
51
+
52
+ ### WSSubscriptionManager
53
+
54
+ The main class that manages WebSocket connections and subscriptions.
55
+
56
+ #### Constructor
57
+
58
+ ```typescript
59
+ new WSSubscriptionManager(handlers: WebSocketHandlers, options?: SubscriptionManagerOptions)
60
+ ```
61
+
62
+ **Parameters:**
63
+ - `handlers` - Event handlers for different WebSocket events
64
+ - `options` - Optional configuration object:
65
+ - `maxMarketsPerWS?: number` - Maximum assets per WebSocket connection (default: 100)
66
+ - `reconnectAndCleanupIntervalMs?: number` - Interval for reconnection attempts (default: 10s)
67
+ - `burstLimiter?: Bottleneck` - Custom rate limiter instance
68
+
69
+ **Connection Management:**
70
+ The WSSubscriptionManager automatically:
71
+ - Groups asset subscriptions into efficient WebSocket connections
72
+ - Handles reconnections when connections drop
73
+ - Manages connection lifecycle and cleanup
74
+ - Balances load across multiple WebSocket groups
75
+
76
+ #### Methods
77
+
78
+ ##### `addSubscriptions(assetIds: string[]): Promise<void>`
79
+
80
+ Adds new asset subscriptions. The manager will:
81
+ - Filter out already subscribed assets
82
+ - Find available connection groups or create new ones
83
+ - Establish WebSocket connections as needed
84
+
85
+ ##### `removeSubscriptions(assetIds: string[]): Promise<void>`
86
+
87
+ Removes asset subscriptions. Connections are kept alive to avoid missing events, and unused groups are cleaned up during the next reconnection cycle.
88
+
89
+ ##### `clearState(): Promise<void>`
90
+
91
+ Clears all subscriptions and state:
92
+ - Removes all asset subscriptions
93
+ - Closes all WebSocket connections
94
+ - Clears the internal order book cache
95
+
96
+ ### WebSocketHandlers
97
+
98
+ Interface defining event handlers for different WebSocket events.
99
+
100
+ ```typescript
101
+ interface WebSocketHandlers {
102
+ // Core Polymarket WebSocket events
103
+ onBook?: (events: BookEvent[]) => Promise<void>;
104
+ onLastTradePrice?: (events: LastTradePriceEvent[]) => Promise<void>;
105
+ onPriceChange?: (events: PriceChangeEvent[]) => Promise<void>;
106
+ onTickSizeChange?: (events: TickSizeChangeEvent[]) => Promise<void>;
107
+
108
+ // Aggregated price update event (recommended for most use cases)
109
+ onPolymarketPriceUpdate?: (events: PolymarketPriceUpdateEvent[]) => Promise<void>;
110
+
111
+ // Connection lifecycle events
112
+ onWSOpen?: (groupId: string, assetIds: string[]) => Promise<void>;
113
+ onWSClose?: (groupId: string, code: number, reason: string) => Promise<void>;
114
+ onError?: (error: Error) => Promise<void>;
115
+ }
116
+ ```
117
+
118
+ #### Key Event Types
119
+
120
+ **PolymarketPriceUpdateEvent** (Recommended)
121
+ - Aggregated price update following Polymarket's display logic
122
+ - Uses midpoint when spread < $0.10, otherwise uses last trade price
123
+ - Includes full order book context
124
+
125
+ **BookEvent**
126
+ - Complete order book snapshots with bids and asks
127
+ - Triggered on significant order book changes
128
+
129
+ **PriceChangeEvent**
130
+ - Individual price level changes in the order book
131
+ - More granular than book events
132
+
133
+ **LastTradePriceEvent**
134
+ - Real-time trade executions
135
+ - Includes trade side, size, and price
136
+
137
+ ## Advanced Usage
138
+
139
+ ### Custom Rate Limiting
140
+
141
+ ```typescript
142
+ import Bottleneck from 'bottleneck';
143
+
144
+ const customLimiter = new Bottleneck({
145
+ reservoir: 3,
146
+ reservoirRefreshAmount: 3,
147
+ reservoirRefreshInterval: 1000,
148
+ maxConcurrent: 3
149
+ });
150
+
151
+ const manager = new WSSubscriptionManager(handlers, {
152
+ burstLimiter: customLimiter
153
+ });
154
+ ```
155
+
156
+ ### Connection Group Configuration
157
+
158
+ ```typescript
159
+ const manager = new WSSubscriptionManager(handlers, {
160
+ maxMarketsPerWS: 50, // Smaller groups for more granular control
161
+ reconnectAndCleanupIntervalMs: 5000 // More frequent reconnection checks
162
+ });
163
+ ```
164
+
165
+ ### Handling All Event Types
166
+
167
+ ```typescript
168
+ const comprehensiveHandlers: WebSocketHandlers = {
169
+ onPolymarketPriceUpdate: async (events) => {
170
+ // Primary price updates for UI display
171
+ events.forEach(event => {
172
+ updatePriceDisplay(event.asset_id, event.price);
173
+ });
174
+ },
175
+
176
+ onBook: async (events) => {
177
+ // Order book depth for trading interfaces
178
+ events.forEach(event => {
179
+ updateOrderBook(event.asset_id, event.bids, event.asks);
180
+ });
181
+ },
182
+
183
+ onLastTradePrice: async (events) => {
184
+ // Real-time trade feed
185
+ events.forEach(event => {
186
+ logTrade(event.asset_id, event.price, event.size, event.side);
187
+ });
188
+ },
189
+
190
+ onWSOpen: async (groupId, assetIds) => {
191
+ console.log(`Connected group ${groupId} with ${assetIds.length} assets`);
192
+ },
193
+
194
+ onWSClose: async (groupId, code, reason) => {
195
+ console.log(`Disconnected group ${groupId}: ${reason} (${code})`);
196
+ },
197
+
198
+ onError: async (error) => {
199
+ console.error('WebSocket error:', error);
200
+ // Implement your error handling/alerting logic
201
+ }
202
+ };
203
+ ```
204
+
205
+ ## Examples
206
+
207
+ Check the [examples](./examples) folder for complete working examples including:
208
+ - Basic price monitoring
209
+ - Market data aggregation
210
+ - Real-time trading interfaces
211
+
212
+ ## Error Handling
213
+
214
+ The library includes comprehensive error handling:
215
+ - Automatic reconnection on connection drops
216
+ - Rate limiting to prevent API blocking
217
+ - Graceful handling of malformed messages
218
+ - User-defined error callbacks for custom handling
219
+
220
+ ## Rate Limits
221
+
222
+ Respects Polymarket's API rate limits:
223
+ - Default: 5 requests per second burst limit
224
+ - Configurable through custom Bottleneck instances
225
+ - Automatic backoff on rate limit hits
226
+
227
+ ## License
228
+
229
+ AGPL-3
230
+
231
+ ## Testing
232
+
233
+ ```bash
234
+ npm test
235
+ ```
236
+
237
+ ## TypeScript Support
238
+
239
+ Full TypeScript definitions included.
240
+
241
+ ## Disclaimer
242
+
243
+ This software is provided "as is", without warranty of any kind, express or implied. The author(s) are not responsible for:
244
+
245
+ - Any financial losses incurred from using this software
246
+ - Trading decisions made based on the data provided
247
+ - Bugs, errors, or inaccuracies in the data
248
+ - System failures or downtime
249
+ - Any other damages arising from the use of this software
250
+
251
+ Use at your own risk. Always verify data independently and never rely solely on automated systems for trading decisions.
@@ -0,0 +1,18 @@
1
+ import { WebSocketHandlers } from './types/PolymarketWebSocket';
2
+ import { SubscriptionManagerOptions } from './types/WebSocketSubscriptions';
3
+ declare class WSSubscriptionManager {
4
+ private handlers;
5
+ private burstLimiter;
6
+ private groupRegistry;
7
+ private bookCache;
8
+ private reconnectAndCleanupIntervalMs;
9
+ private maxMarketsPerWS;
10
+ constructor(userHandlers: WebSocketHandlers, options?: SubscriptionManagerOptions);
11
+ clearState(): Promise<void>;
12
+ private actOnSubscribedEvents;
13
+ addSubscriptions(assetIdsToAdd: string[]): Promise<void>;
14
+ removeSubscriptions(assetIdsToRemove: string[]): Promise<void>;
15
+ private reconnectAndCleanupGroups;
16
+ private createWebSocketClient;
17
+ }
18
+ export { WSSubscriptionManager, WebSocketHandlers };
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WSSubscriptionManager = void 0;
7
+ const ms_1 = __importDefault(require("ms"));
8
+ const lodash_1 = __importDefault(require("lodash"));
9
+ const bottleneck_1 = __importDefault(require("bottleneck"));
10
+ const GroupRegistry_1 = require("./modules/GroupRegistry");
11
+ const OrderBookCache_1 = require("./modules/OrderBookCache");
12
+ const GroupSocket_1 = require("./modules/GroupSocket");
13
+ const logger_1 = require("./logger");
14
+ // Keeping a burst limit under 10/s to avoid rate limiting
15
+ // See https://docs.polymarket.com/quickstart/introduction/rate-limits#api-rate-limits
16
+ const BURST_LIMIT_PER_SECOND = 5;
17
+ const DEFAULT_RECONNECT_AND_CLEANUP_INTERVAL_MS = (0, ms_1.default)('10s');
18
+ const DEFAULT_MAX_MARKETS_PER_WS = 100;
19
+ class WSSubscriptionManager {
20
+ constructor(userHandlers, options) {
21
+ this.groupRegistry = new GroupRegistry_1.GroupRegistry();
22
+ this.bookCache = new OrderBookCache_1.OrderBookCache();
23
+ this.burstLimiter = (options === null || options === void 0 ? void 0 : options.burstLimiter) || new bottleneck_1.default({
24
+ reservoir: BURST_LIMIT_PER_SECOND,
25
+ reservoirRefreshAmount: BURST_LIMIT_PER_SECOND,
26
+ reservoirRefreshInterval: (0, ms_1.default)('1s'),
27
+ maxConcurrent: BURST_LIMIT_PER_SECOND
28
+ });
29
+ this.reconnectAndCleanupIntervalMs = (options === null || options === void 0 ? void 0 : options.reconnectAndCleanupIntervalMs) || DEFAULT_RECONNECT_AND_CLEANUP_INTERVAL_MS;
30
+ this.maxMarketsPerWS = (options === null || options === void 0 ? void 0 : options.maxMarketsPerWS) || DEFAULT_MAX_MARKETS_PER_WS;
31
+ this.handlers = {
32
+ onBook: async (events) => {
33
+ await this.actOnSubscribedEvents(events, userHandlers.onBook);
34
+ },
35
+ onLastTradePrice: async (events) => {
36
+ await this.actOnSubscribedEvents(events, userHandlers.onLastTradePrice);
37
+ },
38
+ onTickSizeChange: async (events) => {
39
+ await this.actOnSubscribedEvents(events, userHandlers.onTickSizeChange);
40
+ },
41
+ onPriceChange: async (events) => {
42
+ await this.actOnSubscribedEvents(events, userHandlers.onPriceChange);
43
+ },
44
+ onPolymarketPriceUpdate: async (events) => {
45
+ await this.actOnSubscribedEvents(events, userHandlers.onPolymarketPriceUpdate);
46
+ },
47
+ onWSClose: userHandlers.onWSClose,
48
+ onWSOpen: userHandlers.onWSOpen,
49
+ onError: userHandlers.onError
50
+ };
51
+ this.burstLimiter.on('error', (err) => {
52
+ var _a, _b;
53
+ (_b = (_a = this.handlers).onError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
54
+ });
55
+ // Check for dead groups every 10s and reconnect them if needed
56
+ setInterval(() => {
57
+ this.reconnectAndCleanupGroups();
58
+ }, this.reconnectAndCleanupIntervalMs);
59
+ }
60
+ /*
61
+ Clears all WebSocket subscriptions and state.
62
+
63
+ This will:
64
+
65
+ 1. Remove all subscriptions and groups
66
+ 2. Close all WebSocket connections
67
+ 3. Clear the order book cache
68
+ */
69
+ async clearState() {
70
+ const previousGroups = await this.groupRegistry.clearAllGroups();
71
+ // Close sockets outside the lock
72
+ for (const group of previousGroups) {
73
+ this.groupRegistry.disconnectGroup(group);
74
+ }
75
+ // Also clear the order book cache
76
+ this.bookCache.clear();
77
+ }
78
+ /*
79
+ This function is called when:
80
+ - a websocket event is received from the Polymarket WS
81
+ - a price update event detected, either by after a 'last_trade_price' event or a 'price_change' event
82
+ depending on the current bid-ask spread (see https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated)
83
+
84
+ The user handlers will be called **ONLY** for assets that are actively subscribed to by any groups.
85
+ */
86
+ async actOnSubscribedEvents(events, action) {
87
+ // Filter out events that are not subscribed to by any groups
88
+ events = lodash_1.default.filter(events, (event) => {
89
+ const groupIndices = this.groupRegistry.getGroupIndicesForAsset(event.asset_id);
90
+ if (groupIndices.length > 1) {
91
+ logger_1.logger.warn({
92
+ message: 'Found multiple groups for asset',
93
+ asset_id: event.asset_id,
94
+ group_indices: groupIndices
95
+ });
96
+ }
97
+ return groupIndices.length > 0;
98
+ });
99
+ await (action === null || action === void 0 ? void 0 : action(events));
100
+ }
101
+ /*
102
+ Edits wsGroups: Adds new subscriptions.
103
+
104
+ - Filters out assets that are already subscribed
105
+ - Finds a group with capacity or creates a new one
106
+ - Creates a new WebSocket client and adds it to the group
107
+ */
108
+ async addSubscriptions(assetIdsToAdd) {
109
+ var _a, _b;
110
+ try {
111
+ const groupIdsToConnect = await this.groupRegistry.addAssets(assetIdsToAdd, this.maxMarketsPerWS);
112
+ for (const groupId of groupIdsToConnect) {
113
+ await this.createWebSocketClient(groupId, this.handlers);
114
+ }
115
+ }
116
+ catch (error) {
117
+ const msg = `Error adding subscriptions: ${error instanceof Error ? error.message : String(error)}`;
118
+ await ((_b = (_a = this.handlers).onError) === null || _b === void 0 ? void 0 : _b.call(_a, new Error(msg)));
119
+ }
120
+ }
121
+ /*
122
+ Edits wsGroups: Removes subscriptions.
123
+ The group will use the updated subscriptions when it reconnects.
124
+ We do that because we don't want to miss events by reconnecting.
125
+ */
126
+ async removeSubscriptions(assetIdsToRemove) {
127
+ var _a, _b;
128
+ try {
129
+ await this.groupRegistry.removeAssets(assetIdsToRemove, this.bookCache);
130
+ }
131
+ catch (error) {
132
+ const errMsg = `Error removing subscriptions: ${error instanceof Error ? error.message : String(error)}`;
133
+ await ((_b = (_a = this.handlers).onError) === null || _b === void 0 ? void 0 : _b.call(_a, new Error(errMsg)));
134
+ }
135
+ }
136
+ /*
137
+ This function runs periodically and:
138
+
139
+ - Tries to reconnect groups that have assets and are disconnected
140
+ - Cleans up groups that have no assets
141
+ */
142
+ async reconnectAndCleanupGroups() {
143
+ var _a, _b;
144
+ try {
145
+ const reconnectIds = await this.groupRegistry.getGroupsToReconnectAndCleanup();
146
+ for (const groupId of reconnectIds) {
147
+ await this.createWebSocketClient(groupId, this.handlers);
148
+ }
149
+ }
150
+ catch (err) {
151
+ await ((_b = (_a = this.handlers).onError) === null || _b === void 0 ? void 0 : _b.call(_a, err));
152
+ }
153
+ }
154
+ async createWebSocketClient(groupId, handlers) {
155
+ var _a, _b;
156
+ const group = this.groupRegistry.findGroupById(groupId);
157
+ /*
158
+ Should never happen, but just in case.
159
+ */
160
+ if (!group) {
161
+ await ((_a = handlers.onError) === null || _a === void 0 ? void 0 : _a.call(handlers, new Error(`Group ${groupId} not found in registry`)));
162
+ return;
163
+ }
164
+ const groupSocket = new GroupSocket_1.GroupSocket(group, this.burstLimiter, this.bookCache, handlers);
165
+ try {
166
+ await groupSocket.connect();
167
+ }
168
+ catch (error) {
169
+ const errorMessage = `Error creating WebSocket client for group ${groupId}: ${error instanceof Error ? error.message : String(error)}`;
170
+ await ((_b = handlers.onError) === null || _b === void 0 ? void 0 : _b.call(handlers, new Error(errorMessage)));
171
+ }
172
+ }
173
+ }
174
+ exports.WSSubscriptionManager = WSSubscriptionManager;
@@ -0,0 +1,3 @@
1
+ export { WSSubscriptionManager, WebSocketHandlers } from './WSSubscriptionManager';
2
+ export * from './types/PolymarketWebSocket';
3
+ export * from './types/WebSocketSubscriptions';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.WSSubscriptionManager = void 0;
18
+ var WSSubscriptionManager_1 = require("./WSSubscriptionManager");
19
+ Object.defineProperty(exports, "WSSubscriptionManager", { enumerable: true, get: function () { return WSSubscriptionManager_1.WSSubscriptionManager; } });
20
+ __exportStar(require("./types/PolymarketWebSocket"), exports);
21
+ __exportStar(require("./types/WebSocketSubscriptions"), exports);
@@ -0,0 +1,2 @@
1
+ import winston from 'winston';
2
+ export declare const logger: winston.Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = void 0;
7
+ const winston_1 = __importDefault(require("winston"));
8
+ // Override with LOG_LEVEL environment variable (e.g., LOG_LEVEL=info npm start)
9
+ exports.logger = winston_1.default.createLogger({
10
+ level: process.env.LOG_LEVEL || 'error',
11
+ format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.colorize(), winston_1.default.format.printf(({ level, message, timestamp, ...rest }) => {
12
+ // Ensure consistent order: timestamp, level, message, then rest of fields
13
+ const restString = Object.keys(rest)
14
+ .filter(key => key !== 'service') // Exclude service since we add it in defaultMeta
15
+ .sort()
16
+ .map(key => `${key}: ${JSON.stringify(rest[key])}`)
17
+ .join(', ');
18
+ return `${timestamp} ${level}: ${message}${restString ? ` (${restString})` : ''}`;
19
+ })),
20
+ defaultMeta: { service: 'poly-websockets' },
21
+ transports: [
22
+ new winston_1.default.transports.Console({
23
+ format: winston_1.default.format.combine(winston_1.default.format.colorize({
24
+ all: true,
25
+ colors: {
26
+ error: 'red',
27
+ warn: 'yellow',
28
+ info: 'cyan',
29
+ debug: 'green'
30
+ }
31
+ }))
32
+ })
33
+ ]
34
+ });
@@ -0,0 +1,85 @@
1
+ import { WebSocketGroup } from '../types/WebSocketSubscriptions';
2
+ import { OrderBookCache } from './OrderBookCache';
3
+ export declare class GroupRegistry {
4
+ /**
5
+ * Atomic mutate helper.
6
+ *
7
+ * @param fn - The function to run atomically.
8
+ * @returns The result of the function.
9
+ */
10
+ mutate<T>(fn: (groups: WebSocketGroup[]) => T | Promise<T>): Promise<T>;
11
+ /**
12
+ * Read-only copy of the registry.
13
+ *
14
+ * Only to be used in test suite.
15
+ */
16
+ snapshot(): WebSocketGroup[];
17
+ /**
18
+ * Find the first group with capacity to hold new assets.
19
+ *
20
+ * Returns the groupId if found, otherwise null.
21
+ */
22
+ findGroupWithCapacity(newAssetLen: number, maxPerWS: number): string | null;
23
+ /**
24
+ * Get the indices of all groups that contain the asset.
25
+ *
26
+ * Returns an array of indices.
27
+ */
28
+ getGroupIndicesForAsset(assetId: string): number[];
29
+ /**
30
+ * Check if any group contains the asset.
31
+ */
32
+ hasAsset(assetId: string): boolean;
33
+ /**
34
+ * Find the group by groupId.
35
+ *
36
+ * Returns the group if found, otherwise undefined.
37
+ */
38
+ findGroupById(groupId: string): WebSocketGroup | undefined;
39
+ /**
40
+ * Atomically remove **all** groups from the registry and return them so the
41
+ * caller can perform any asynchronous cleanup (closing sockets, etc.)
42
+ * outside the lock.
43
+ *
44
+ * Returns the removed groups.
45
+ */
46
+ clearAllGroups(): Promise<WebSocketGroup[]>;
47
+ /**
48
+ * Add new asset subscriptions.
49
+ *
50
+ * – Ignores assets that are already subscribed.
51
+ * – Either reuses an existing group with capacity or creates new groups (size ≤ maxPerWS).
52
+ * – If appending to a group:
53
+ * - A new group is created with the updated assetIds.
54
+ * - The old group is marked for cleanup.
55
+ * - The group is added to the list of groups to connect.
56
+ *
57
+ * @param assetIds - The assetIds to add.
58
+ * @param maxPerWS - The maximum number of assets per WebSocket group.
59
+ * @returns An array of *new* groupIds that need websocket connections.
60
+ */
61
+ addAssets(assetIds: string[], maxPerWS: number): Promise<string[]>;
62
+ /**
63
+ * Remove asset subscriptions from every group that contains the asset.
64
+ *
65
+ * It should be only one group that contains the asset, we search all of them
66
+ * regardless.
67
+ *
68
+ * Returns the list of assetIds that were removed.
69
+ */
70
+ removeAssets(assetIds: string[], bookCache: OrderBookCache): Promise<string[]>;
71
+ /**
72
+ * Disconnect a group.
73
+ */
74
+ disconnectGroup(group: WebSocketGroup): void;
75
+ /**
76
+ * Check status of groups and reconnect or cleanup as needed.
77
+ *
78
+ * – Empty groups are removed from the global array and returned.
79
+ * – Dead (but non-empty) groups are reset so that caller can reconnect them.
80
+ * – Pending groups are returned so that caller can connect them.
81
+ *
82
+ * Returns an array of group IDs that need to be reconnected, after cleaning up empty and cleanup-marked groups.
83
+ */
84
+ getGroupsToReconnectAndCleanup(): Promise<string[]>;
85
+ }