pmxtjs 2.35.29 → 2.35.32

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 (42) hide show
  1. package/dist/esm/generated/src/apis/DefaultApi.d.ts +33 -1
  2. package/dist/esm/generated/src/apis/DefaultApi.js +48 -1
  3. package/dist/esm/generated/src/models/WatchOrderBooks200Response.d.ts +48 -0
  4. package/dist/esm/generated/src/models/WatchOrderBooks200Response.js +48 -0
  5. package/dist/esm/generated/src/models/WatchOrderBooksRequest.d.ts +40 -0
  6. package/dist/esm/generated/src/models/WatchOrderBooksRequest.js +47 -0
  7. package/dist/esm/generated/src/models/WatchOrderBooksRequestArgsInner.d.ts +21 -0
  8. package/dist/esm/generated/src/models/WatchOrderBooksRequestArgsInner.js +47 -0
  9. package/dist/esm/generated/src/models/index.d.ts +3 -0
  10. package/dist/esm/generated/src/models/index.js +3 -0
  11. package/dist/esm/pmxt/client.d.ts +42 -0
  12. package/dist/esm/pmxt/client.js +149 -4
  13. package/dist/esm/pmxt/ws-client.d.ts +37 -0
  14. package/dist/esm/pmxt/ws-client.js +272 -0
  15. package/dist/generated/src/apis/DefaultApi.d.ts +33 -1
  16. package/dist/generated/src/apis/DefaultApi.js +48 -1
  17. package/dist/generated/src/models/WatchOrderBooks200Response.d.ts +48 -0
  18. package/dist/generated/src/models/WatchOrderBooks200Response.js +55 -0
  19. package/dist/generated/src/models/WatchOrderBooksRequest.d.ts +40 -0
  20. package/dist/generated/src/models/WatchOrderBooksRequest.js +54 -0
  21. package/dist/generated/src/models/WatchOrderBooksRequestArgsInner.d.ts +21 -0
  22. package/dist/generated/src/models/WatchOrderBooksRequestArgsInner.js +53 -0
  23. package/dist/generated/src/models/index.d.ts +3 -0
  24. package/dist/generated/src/models/index.js +3 -0
  25. package/dist/pmxt/client.d.ts +42 -0
  26. package/dist/pmxt/client.js +149 -4
  27. package/dist/pmxt/ws-client.d.ts +37 -0
  28. package/dist/pmxt/ws-client.js +276 -0
  29. package/generated/.openapi-generator/FILES +6 -0
  30. package/generated/docs/DefaultApi.md +71 -0
  31. package/generated/docs/WatchOrderBooks200Response.md +38 -0
  32. package/generated/docs/WatchOrderBooksRequest.md +36 -0
  33. package/generated/docs/WatchOrderBooksRequestArgsInner.md +32 -0
  34. package/generated/package.json +1 -1
  35. package/generated/src/apis/DefaultApi.ts +71 -0
  36. package/generated/src/models/WatchOrderBooks200Response.ts +96 -0
  37. package/generated/src/models/WatchOrderBooksRequest.ts +89 -0
  38. package/generated/src/models/WatchOrderBooksRequestArgsInner.ts +59 -0
  39. package/generated/src/models/index.ts +3 -0
  40. package/package.json +2 -2
  41. package/pmxt/client.ts +181 -8
  42. package/pmxt/ws-client.ts +347 -0
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /**
5
+ * PMXT Sidecar API
6
+ * A unified local sidecar API for prediction markets (Polymarket, Kalshi, Limitless). This API acts as a JSON-RPC-style gateway. Each endpoint corresponds to a specific method on the generic exchange implementation.
7
+ *
8
+ * The version of the OpenAPI document: 0.4.4
9
+ *
10
+ *
11
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
12
+ * https://openapi-generator.tech
13
+ * Do not edit the class manually.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.WatchOrderBooksRequestArgsInnerFromJSON = WatchOrderBooksRequestArgsInnerFromJSON;
17
+ exports.WatchOrderBooksRequestArgsInnerFromJSONTyped = WatchOrderBooksRequestArgsInnerFromJSONTyped;
18
+ exports.WatchOrderBooksRequestArgsInnerToJSON = WatchOrderBooksRequestArgsInnerToJSON;
19
+ exports.WatchOrderBooksRequestArgsInnerToJSONTyped = WatchOrderBooksRequestArgsInnerToJSONTyped;
20
+ function WatchOrderBooksRequestArgsInnerFromJSON(json) {
21
+ return WatchOrderBooksRequestArgsInnerFromJSONTyped(json, false);
22
+ }
23
+ function WatchOrderBooksRequestArgsInnerFromJSONTyped(json, ignoreDiscriminator) {
24
+ if (json == null) {
25
+ return json;
26
+ }
27
+ if (typeof json === 'number') {
28
+ return json;
29
+ }
30
+ if (Array.isArray(json)) {
31
+ if (json.every(item => typeof item === 'string')) {
32
+ return json;
33
+ }
34
+ }
35
+ return {};
36
+ }
37
+ function WatchOrderBooksRequestArgsInnerToJSON(json) {
38
+ return WatchOrderBooksRequestArgsInnerToJSONTyped(json, false);
39
+ }
40
+ function WatchOrderBooksRequestArgsInnerToJSONTyped(value, ignoreDiscriminator = false) {
41
+ if (value == null) {
42
+ return value;
43
+ }
44
+ if (typeof value === 'number') {
45
+ return value;
46
+ }
47
+ if (Array.isArray(value)) {
48
+ if (value.every(item => typeof item === 'string')) {
49
+ return value;
50
+ }
51
+ }
52
+ return {};
53
+ }
@@ -91,4 +91,7 @@ export * from './WatchAddressRequest';
91
91
  export * from './WatchAddressRequestArgsInner';
92
92
  export * from './WatchOrderBookRequest';
93
93
  export * from './WatchOrderBookRequestArgsInner';
94
+ export * from './WatchOrderBooks200Response';
95
+ export * from './WatchOrderBooksRequest';
96
+ export * from './WatchOrderBooksRequestArgsInner';
94
97
  export * from './WatchTradesRequest';
@@ -109,4 +109,7 @@ __exportStar(require("./WatchAddressRequest"), exports);
109
109
  __exportStar(require("./WatchAddressRequestArgsInner"), exports);
110
110
  __exportStar(require("./WatchOrderBookRequest"), exports);
111
111
  __exportStar(require("./WatchOrderBookRequestArgsInner"), exports);
112
+ __exportStar(require("./WatchOrderBooks200Response"), exports);
113
+ __exportStar(require("./WatchOrderBooksRequest"), exports);
114
+ __exportStar(require("./WatchOrderBooksRequestArgsInner"), exports);
112
115
  __exportStar(require("./WatchTradesRequest"), exports);
@@ -75,6 +75,10 @@ export declare abstract class Exchange {
75
75
  * POST directly and skip the GET probe for the lifetime of this client.
76
76
  */
77
77
  private _getReadsUnsupported;
78
+ /** Shared WebSocket client for streaming methods (lazy). */
79
+ private _wsClient;
80
+ /** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
81
+ private _wsUnsupported;
78
82
  constructor(exchangeName: string, options?: ExchangeOptions);
79
83
  private initializeServer;
80
84
  protected handleResponse(response: any): any;
@@ -96,6 +100,18 @@ export declare abstract class Exchange {
96
100
  * attempts to restart the sidecar.
97
101
  */
98
102
  private fetchWithRetry;
103
+ /**
104
+ * Return the shared WebSocket client, creating it on first use.
105
+ *
106
+ * Returns `null` if the sidecar /ws endpoint was previously found
107
+ * to be unavailable, letting callers fall back to HTTP.
108
+ */
109
+ private getOrCreateWs;
110
+ /**
111
+ * Attempt to use the WS transport for a watch method.
112
+ * Returns the raw data on success, or `null` if WS is unavailable.
113
+ */
114
+ private watchViaWs;
99
115
  /**
100
116
  * Call an exchange-specific REST endpoint by its operationId.
101
117
  * This provides direct access to all implicit API methods defined in
@@ -207,6 +223,32 @@ export declare abstract class Exchange {
207
223
  * ```
208
224
  */
209
225
  watchOrderBook(outcomeId: string | MarketOutcome, limit?: number): Promise<OrderBook>;
226
+ /**
227
+ * Watch real-time order book updates for multiple outcomes at once.
228
+ *
229
+ * Returns a record mapping each outcome ID (ticker) to its latest
230
+ * order book snapshot. Call repeatedly in a loop to stream updates
231
+ * (CCXT Pro pattern).
232
+ *
233
+ * Prefers the sidecar WebSocket transport when available, falling
234
+ * back to HTTP POST for older sidecars.
235
+ *
236
+ * @param outcomeIds - Array of outcome IDs (or MarketOutcome objects)
237
+ * @param limit - Optional depth limit for each order book
238
+ * @returns Record mapping ticker to OrderBook
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const ids = markets.slice(0, 3).map(m => m.outcomes[0].outcomeId);
243
+ * while (true) {
244
+ * const books = await exchange.watchOrderBooks(ids);
245
+ * for (const [ticker, ob] of Object.entries(books)) {
246
+ * console.log(`${ticker}: bid=${ob.bids[0]?.price}`);
247
+ * }
248
+ * }
249
+ * ```
250
+ */
251
+ watchOrderBooks(outcomeIds: (string | MarketOutcome)[], limit?: number): Promise<Record<string, OrderBook>>;
210
252
  /**
211
253
  * Watch real-time trade updates via WebSocket.
212
254
  *
@@ -12,6 +12,7 @@ const models_js_1 = require("./models.js");
12
12
  const server_manager_js_1 = require("./server-manager.js");
13
13
  const errors_js_1 = require("./errors.js");
14
14
  const constants_js_1 = require("./constants.js");
15
+ const ws_client_js_1 = require("./ws-client.js");
15
16
  /**
16
17
  * Resolve a MarketOutcome shorthand to a plain outcome ID string.
17
18
  * Accepts either a raw string ID or a MarketOutcome object.
@@ -248,6 +249,10 @@ class Exchange {
248
249
  * POST directly and skip the GET probe for the lifetime of this client.
249
250
  */
250
251
  _getReadsUnsupported = false;
252
+ /** Shared WebSocket client for streaming methods (lazy). */
253
+ _wsClient = null;
254
+ /** Sticky flag: true if the sidecar /ws endpoint is unavailable. */
255
+ _wsUnsupported = false;
251
256
  constructor(exchangeName, options = {}) {
252
257
  this.exchangeName = exchangeName.toLowerCase();
253
258
  this.apiKey = options.apiKey;
@@ -383,6 +388,58 @@ class Exchange {
383
388
  }
384
389
  throw lastError;
385
390
  }
391
+ /**
392
+ * Return the shared WebSocket client, creating it on first use.
393
+ *
394
+ * Returns `null` if the sidecar /ws endpoint was previously found
395
+ * to be unavailable, letting callers fall back to HTTP.
396
+ */
397
+ async getOrCreateWs() {
398
+ if (this._wsUnsupported)
399
+ return null;
400
+ if (this._wsClient?.connected)
401
+ return this._wsClient;
402
+ const host = this.resolveBaseUrl();
403
+ const accessToken = this.serverManager.getAccessToken();
404
+ const client = new ws_client_js_1.SidecarWsClient(host, accessToken || undefined);
405
+ try {
406
+ // Trigger connection to validate the endpoint exists.
407
+ // subscribe() calls ensureConnected internally, but we want
408
+ // to detect failure eagerly so we can set _wsUnsupported.
409
+ await client.subscribe(this.exchangeName, "_ping", [], undefined, 3000).catch(() => {
410
+ // Expected -- no _ping method. The connection itself
411
+ // succeeded if we got a WS error frame back. If the
412
+ // connection itself failed, we'll catch below.
413
+ });
414
+ // If we got here without the connect promise rejecting,
415
+ // the WS endpoint exists.
416
+ if (!client.connected) {
417
+ throw new Error("WS handshake failed");
418
+ }
419
+ }
420
+ catch {
421
+ this._wsUnsupported = true;
422
+ client.close();
423
+ return null;
424
+ }
425
+ this._wsClient = client;
426
+ return this._wsClient;
427
+ }
428
+ /**
429
+ * Attempt to use the WS transport for a watch method.
430
+ * Returns the raw data on success, or `null` if WS is unavailable.
431
+ */
432
+ async watchViaWs(method, args) {
433
+ const ws = await this.getOrCreateWs();
434
+ if (!ws)
435
+ return null;
436
+ try {
437
+ return await ws.subscribe(this.exchangeName, method, args, this.getCredentials());
438
+ }
439
+ catch {
440
+ return null;
441
+ }
442
+ }
386
443
  // Low-Level API Access
387
444
  /**
388
445
  * Call an exchange-specific REST endpoint by its operationId.
@@ -1328,11 +1385,17 @@ class Exchange {
1328
1385
  async watchOrderBook(outcomeId, limit) {
1329
1386
  await this.initPromise;
1330
1387
  const resolvedOutcomeId = resolveOutcomeId(outcomeId);
1388
+ const args = [resolvedOutcomeId];
1389
+ if (limit !== undefined) {
1390
+ args.push(limit);
1391
+ }
1392
+ // Try WebSocket transport first
1393
+ const wsData = await this.watchViaWs("watchOrderBook", args);
1394
+ if (wsData !== null) {
1395
+ return convertOrderBook(wsData);
1396
+ }
1397
+ // HTTP fallback
1331
1398
  try {
1332
- const args = [resolvedOutcomeId];
1333
- if (limit !== undefined) {
1334
- args.push(limit);
1335
- }
1336
1399
  const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/watchOrderBook`, {
1337
1400
  method: 'POST',
1338
1401
  headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
@@ -1355,6 +1418,88 @@ class Exchange {
1355
1418
  throw new errors_js_1.PmxtError(`Failed to watch order book: ${error}`);
1356
1419
  }
1357
1420
  }
1421
+ /**
1422
+ * Watch real-time order book updates for multiple outcomes at once.
1423
+ *
1424
+ * Returns a record mapping each outcome ID (ticker) to its latest
1425
+ * order book snapshot. Call repeatedly in a loop to stream updates
1426
+ * (CCXT Pro pattern).
1427
+ *
1428
+ * Prefers the sidecar WebSocket transport when available, falling
1429
+ * back to HTTP POST for older sidecars.
1430
+ *
1431
+ * @param outcomeIds - Array of outcome IDs (or MarketOutcome objects)
1432
+ * @param limit - Optional depth limit for each order book
1433
+ * @returns Record mapping ticker to OrderBook
1434
+ *
1435
+ * @example
1436
+ * ```typescript
1437
+ * const ids = markets.slice(0, 3).map(m => m.outcomes[0].outcomeId);
1438
+ * while (true) {
1439
+ * const books = await exchange.watchOrderBooks(ids);
1440
+ * for (const [ticker, ob] of Object.entries(books)) {
1441
+ * console.log(`${ticker}: bid=${ob.bids[0]?.price}`);
1442
+ * }
1443
+ * }
1444
+ * ```
1445
+ */
1446
+ async watchOrderBooks(outcomeIds, limit) {
1447
+ await this.initPromise;
1448
+ const resolvedIds = outcomeIds.map(resolveOutcomeId);
1449
+ const args = [resolvedIds];
1450
+ if (limit !== undefined) {
1451
+ args.push(limit);
1452
+ }
1453
+ // Try WebSocket transport first
1454
+ const ws = await this.getOrCreateWs();
1455
+ if (ws) {
1456
+ try {
1457
+ const rawResult = await ws.subscribeBatch(this.exchangeName, "watchOrderBooks", args, this.getCredentials());
1458
+ if (rawResult && typeof rawResult === "object") {
1459
+ const result = {};
1460
+ for (const [k, v] of Object.entries(rawResult)) {
1461
+ if (v && typeof v === "object") {
1462
+ result[k] = convertOrderBook(v);
1463
+ }
1464
+ }
1465
+ return result;
1466
+ }
1467
+ }
1468
+ catch {
1469
+ // fall through to HTTP
1470
+ }
1471
+ }
1472
+ // HTTP fallback
1473
+ try {
1474
+ const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/watchOrderBooks`, {
1475
+ method: 'POST',
1476
+ headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
1477
+ body: JSON.stringify({ args, credentials: this.getCredentials() }),
1478
+ });
1479
+ if (!response.ok) {
1480
+ const body = await response.json().catch(() => ({}));
1481
+ if (body.error && typeof body.error === "object") {
1482
+ throw (0, errors_js_1.fromServerError)(body.error);
1483
+ }
1484
+ throw new errors_js_1.PmxtError(body.error?.message || response.statusText);
1485
+ }
1486
+ const json = await response.json();
1487
+ const data = this.handleResponse(json);
1488
+ if (data && typeof data === "object") {
1489
+ const result = {};
1490
+ for (const [k, v] of Object.entries(data)) {
1491
+ result[k] = convertOrderBook(v);
1492
+ }
1493
+ return result;
1494
+ }
1495
+ return {};
1496
+ }
1497
+ catch (error) {
1498
+ if (error instanceof errors_js_1.PmxtError)
1499
+ throw error;
1500
+ throw new errors_js_1.PmxtError(`Failed to watch order books: ${error}`);
1501
+ }
1502
+ }
1358
1503
  /**
1359
1504
  * Watch real-time trade updates via WebSocket.
1360
1505
  *
@@ -0,0 +1,37 @@
1
+ /**
2
+ * WebSocket client for streaming methods.
3
+ *
4
+ * Provides a multiplexed WebSocket connection to the sidecar server,
5
+ * used by watchOrderBook and watchOrderBooks as an alternative to
6
+ * HTTP long-polling. Falls back to HTTP transparently when the sidecar
7
+ * does not support the /ws endpoint.
8
+ */
9
+ /**
10
+ * Multiplexed WebSocket client for the pmxt sidecar.
11
+ *
12
+ * Lazily connects to ws://{host}/ws?token={accessToken}. A single
13
+ * WebSocket connection is shared across all streaming subscriptions.
14
+ */
15
+ export declare class SidecarWsClient {
16
+ private ws;
17
+ private host;
18
+ private accessToken;
19
+ private closed;
20
+ /** requestId -> latest data payload */
21
+ private dataStore;
22
+ /** requestId -> subscription metadata */
23
+ private subscriptions;
24
+ /** (method:symbolKey) -> requestId -- avoids duplicate subscribes */
25
+ private activeSubs;
26
+ private connectPromise;
27
+ constructor(host: string, accessToken?: string);
28
+ private ensureConnected;
29
+ private connect;
30
+ private getWebSocketConstructor;
31
+ private dispatch;
32
+ subscribe(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<any>;
33
+ subscribeBatch(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<Record<string, any>>;
34
+ close(): void;
35
+ get connected(): boolean;
36
+ private waitForData;
37
+ }
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ /**
3
+ * WebSocket client for streaming methods.
4
+ *
5
+ * Provides a multiplexed WebSocket connection to the sidecar server,
6
+ * used by watchOrderBook and watchOrderBooks as an alternative to
7
+ * HTTP long-polling. Falls back to HTTP transparently when the sidecar
8
+ * does not support the /ws endpoint.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SidecarWsClient = void 0;
12
+ const errors_js_1 = require("./errors.js");
13
+ /**
14
+ * Multiplexed WebSocket client for the pmxt sidecar.
15
+ *
16
+ * Lazily connects to ws://{host}/ws?token={accessToken}. A single
17
+ * WebSocket connection is shared across all streaming subscriptions.
18
+ */
19
+ class SidecarWsClient {
20
+ ws = null;
21
+ host;
22
+ accessToken;
23
+ closed = false;
24
+ /** requestId -> latest data payload */
25
+ dataStore = new Map();
26
+ /** requestId -> subscription metadata */
27
+ subscriptions = new Map();
28
+ /** (method:symbolKey) -> requestId -- avoids duplicate subscribes */
29
+ activeSubs = new Map();
30
+ connectPromise = null;
31
+ constructor(host, accessToken) {
32
+ this.host = host;
33
+ this.accessToken = accessToken;
34
+ }
35
+ // ------------------------------------------------------------------
36
+ // Connection lifecycle
37
+ // ------------------------------------------------------------------
38
+ async ensureConnected() {
39
+ if (this.ws && !this.closed)
40
+ return;
41
+ if (this.connectPromise)
42
+ return this.connectPromise;
43
+ this.connectPromise = this.connect();
44
+ try {
45
+ await this.connectPromise;
46
+ }
47
+ finally {
48
+ this.connectPromise = null;
49
+ }
50
+ }
51
+ connect() {
52
+ return new Promise((resolve, reject) => {
53
+ let hostPart = this.host;
54
+ let scheme = "ws";
55
+ if (hostPart.startsWith("https://")) {
56
+ hostPart = hostPart.slice("https://".length);
57
+ scheme = "wss";
58
+ }
59
+ else if (hostPart.startsWith("http://")) {
60
+ hostPart = hostPart.slice("http://".length);
61
+ }
62
+ let url = `${scheme}://${hostPart}/ws`;
63
+ if (this.accessToken) {
64
+ url = `${url}?token=${this.accessToken}`;
65
+ }
66
+ // Use the ws package in Node.js, native WebSocket in browsers
67
+ const WsConstructor = this.getWebSocketConstructor();
68
+ if (!WsConstructor) {
69
+ reject(new errors_js_1.PmxtError("No WebSocket implementation available"));
70
+ return;
71
+ }
72
+ const ws = new WsConstructor(url);
73
+ this.closed = false;
74
+ ws.onopen = () => {
75
+ this.ws = ws;
76
+ resolve();
77
+ };
78
+ ws.onerror = (err) => {
79
+ if (!this.ws) {
80
+ // Connection failed during handshake
81
+ reject(new errors_js_1.PmxtError(`WebSocket connection failed: ${err.message || err}`));
82
+ }
83
+ };
84
+ ws.onclose = () => {
85
+ this.closed = true;
86
+ this.ws = null;
87
+ };
88
+ ws.onmessage = (event) => {
89
+ try {
90
+ const data = typeof event.data === "string"
91
+ ? event.data
92
+ : event.data.toString();
93
+ const msg = JSON.parse(data);
94
+ this.dispatch(msg);
95
+ }
96
+ catch {
97
+ // Ignore unparseable frames
98
+ }
99
+ };
100
+ });
101
+ }
102
+ getWebSocketConstructor() {
103
+ // Browser / Deno / Bun
104
+ if (typeof globalThis !== "undefined" && globalThis.WebSocket) {
105
+ return globalThis.WebSocket;
106
+ }
107
+ // Node.js -- try to require ws
108
+ try {
109
+ // Dynamic require to avoid bundler issues
110
+ const wsModule = require("ws");
111
+ return wsModule.default || wsModule;
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ dispatch(msg) {
118
+ const eventType = msg.event;
119
+ const requestId = msg.id;
120
+ if (eventType === "error" && requestId) {
121
+ const sub = this.subscriptions.get(requestId);
122
+ if (sub?.reject) {
123
+ sub.reject(new errors_js_1.PmxtError(msg.error?.message || "WebSocket subscription error"));
124
+ sub.reject = null;
125
+ sub.resolve = null;
126
+ }
127
+ return;
128
+ }
129
+ if (eventType === "subscribed") {
130
+ // Acknowledgement -- nothing to do
131
+ return;
132
+ }
133
+ if (eventType === "data" && requestId) {
134
+ const symbol = msg.symbol || "";
135
+ const data = msg.data || {};
136
+ // Store by (requestId:symbol) for batch methods
137
+ this.dataStore.set(`${requestId}:${symbol}`, data);
138
+ // Store by requestId alone for single-symbol methods
139
+ this.dataStore.set(requestId, data);
140
+ const sub = this.subscriptions.get(requestId);
141
+ if (sub?.resolve) {
142
+ sub.resolve(data);
143
+ sub.resolve = null;
144
+ sub.reject = null;
145
+ }
146
+ }
147
+ }
148
+ // ------------------------------------------------------------------
149
+ // Public API
150
+ // ------------------------------------------------------------------
151
+ async subscribe(exchange, method, args, credentials, timeoutMs = 30000) {
152
+ const firstArg = args[0] ?? "";
153
+ const subKey = Array.isArray(firstArg)
154
+ ? `${method}:${[...firstArg].sort().join(",")}`
155
+ : `${method}:${firstArg}`;
156
+ // Reuse existing subscription
157
+ const existingId = this.activeSubs.get(subKey);
158
+ if (existingId && this.subscriptions.has(existingId)) {
159
+ return this.waitForData(existingId, timeoutMs);
160
+ }
161
+ await this.ensureConnected();
162
+ const requestId = `req-${Math.random().toString(36).slice(2, 14)}`;
163
+ const symbols = Array.isArray(firstArg) ? firstArg : firstArg ? [firstArg] : [];
164
+ const sub = {
165
+ requestId,
166
+ method,
167
+ symbols,
168
+ resolve: null,
169
+ reject: null,
170
+ };
171
+ this.subscriptions.set(requestId, sub);
172
+ this.activeSubs.set(subKey, requestId);
173
+ const message = {
174
+ id: requestId,
175
+ action: "subscribe",
176
+ exchange,
177
+ method,
178
+ args,
179
+ };
180
+ if (credentials) {
181
+ message.credentials = credentials;
182
+ }
183
+ this.ws.send(JSON.stringify(message));
184
+ return this.waitForData(requestId, timeoutMs);
185
+ }
186
+ async subscribeBatch(exchange, method, args, credentials, timeoutMs = 30000) {
187
+ const symbols = Array.isArray(args[0]) ? args[0] : [];
188
+ await this.ensureConnected();
189
+ const requestId = `req-${Math.random().toString(36).slice(2, 14)}`;
190
+ const sub = {
191
+ requestId,
192
+ method,
193
+ symbols,
194
+ resolve: null,
195
+ reject: null,
196
+ };
197
+ this.subscriptions.set(requestId, sub);
198
+ const message = {
199
+ id: requestId,
200
+ action: "subscribe",
201
+ exchange,
202
+ method,
203
+ args,
204
+ };
205
+ if (credentials) {
206
+ message.credentials = credentials;
207
+ }
208
+ this.ws.send(JSON.stringify(message));
209
+ // Wait for first data event
210
+ await this.waitForData(requestId, timeoutMs);
211
+ // Collect per-symbol data
212
+ const result = {};
213
+ for (const symbol of symbols) {
214
+ const storeKey = `${requestId}:${symbol}`;
215
+ const data = this.dataStore.get(storeKey);
216
+ if (data !== undefined) {
217
+ result[symbol] = data;
218
+ }
219
+ }
220
+ // If no per-symbol data, return the single data event as-is
221
+ if (Object.keys(result).length === 0) {
222
+ const data = this.dataStore.get(requestId);
223
+ if (data && typeof data === "object") {
224
+ return data;
225
+ }
226
+ }
227
+ return result;
228
+ }
229
+ close() {
230
+ this.closed = true;
231
+ if (this.ws) {
232
+ try {
233
+ this.ws.close();
234
+ }
235
+ catch {
236
+ // ignore
237
+ }
238
+ this.ws = null;
239
+ }
240
+ }
241
+ get connected() {
242
+ return this.ws !== null && !this.closed;
243
+ }
244
+ // ------------------------------------------------------------------
245
+ // Internal
246
+ // ------------------------------------------------------------------
247
+ waitForData(requestId, timeoutMs) {
248
+ // Check if data is already available
249
+ const existing = this.dataStore.get(requestId);
250
+ if (existing !== undefined) {
251
+ this.dataStore.delete(requestId);
252
+ return Promise.resolve(existing);
253
+ }
254
+ return new Promise((resolve, reject) => {
255
+ const sub = this.subscriptions.get(requestId);
256
+ if (!sub) {
257
+ reject(new errors_js_1.PmxtError("Subscription not found"));
258
+ return;
259
+ }
260
+ const timer = setTimeout(() => {
261
+ sub.resolve = null;
262
+ sub.reject = null;
263
+ reject(new errors_js_1.PmxtError(`Timeout waiting for WebSocket data (method=${sub.method})`));
264
+ }, timeoutMs);
265
+ sub.resolve = (data) => {
266
+ clearTimeout(timer);
267
+ resolve(data);
268
+ };
269
+ sub.reject = (err) => {
270
+ clearTimeout(timer);
271
+ reject(err);
272
+ };
273
+ });
274
+ }
275
+ }
276
+ exports.SidecarWsClient = SidecarWsClient;
@@ -96,6 +96,9 @@ docs/WatchAddressRequest.md
96
96
  docs/WatchAddressRequestArgsInner.md
97
97
  docs/WatchOrderBookRequest.md
98
98
  docs/WatchOrderBookRequestArgsInner.md
99
+ docs/WatchOrderBooks200Response.md
100
+ docs/WatchOrderBooksRequest.md
101
+ docs/WatchOrderBooksRequestArgsInner.md
99
102
  docs/WatchTradesRequest.md
100
103
  package.json
101
104
  src/apis/DefaultApi.ts
@@ -194,6 +197,9 @@ src/models/WatchAddressRequest.ts
194
197
  src/models/WatchAddressRequestArgsInner.ts
195
198
  src/models/WatchOrderBookRequest.ts
196
199
  src/models/WatchOrderBookRequestArgsInner.ts
200
+ src/models/WatchOrderBooks200Response.ts
201
+ src/models/WatchOrderBooksRequest.ts
202
+ src/models/WatchOrderBooksRequestArgsInner.ts
197
203
  src/models/WatchTradesRequest.ts
198
204
  src/models/index.ts
199
205
  src/runtime.ts