pmxt-core 2.37.0 → 2.37.2

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 (32) hide show
  1. package/dist/exchanges/baozi/websocket.d.ts +6 -0
  2. package/dist/exchanges/baozi/websocket.js +7 -1
  3. package/dist/exchanges/kalshi/api.d.ts +1 -1
  4. package/dist/exchanges/kalshi/api.js +1 -1
  5. package/dist/exchanges/kalshi/websocket.d.ts +2 -0
  6. package/dist/exchanges/kalshi/websocket.js +43 -20
  7. package/dist/exchanges/limitless/api.d.ts +1 -1
  8. package/dist/exchanges/limitless/api.js +1 -1
  9. package/dist/exchanges/myriad/api.d.ts +1 -1
  10. package/dist/exchanges/myriad/api.js +1 -1
  11. package/dist/exchanges/opinion/api.d.ts +1 -1
  12. package/dist/exchanges/opinion/api.js +1 -1
  13. package/dist/exchanges/opinion/websocket.d.ts +2 -0
  14. package/dist/exchanges/opinion/websocket.js +23 -6
  15. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  16. package/dist/exchanges/polymarket/api-clob.js +1 -1
  17. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  18. package/dist/exchanges/polymarket/api-data.js +1 -1
  19. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  20. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  21. package/dist/exchanges/polymarket/websocket.d.ts +2 -0
  22. package/dist/exchanges/polymarket/websocket.js +5 -2
  23. package/dist/exchanges/polymarket_us/websocket.d.ts +6 -1
  24. package/dist/exchanges/polymarket_us/websocket.js +8 -3
  25. package/dist/exchanges/probable/api.d.ts +1 -1
  26. package/dist/exchanges/probable/api.js +1 -1
  27. package/dist/exchanges/probable/websocket.d.ts +2 -0
  28. package/dist/exchanges/probable/websocket.js +3 -1
  29. package/dist/server/ws-handler.js +12 -3
  30. package/dist/utils/watch-timeout.d.ts +18 -0
  31. package/dist/utils/watch-timeout.js +35 -0
  32. package/package.json +3 -3
@@ -1,13 +1,19 @@
1
1
  import { Connection } from '@solana/web3.js';
2
2
  import { OrderBook } from '../../types';
3
+ export interface BaoziWebSocketConfig {
4
+ /** Timeout in ms for watch methods to receive data (default: 30000). 0 = no timeout. */
5
+ watchTimeoutMs?: number;
6
+ }
3
7
  /**
4
8
  * Uses Solana's onAccountChange to watch market PDA updates.
5
9
  * When the account data changes (new bet placed), we re-parse
6
10
  * and emit a new synthetic order book.
7
11
  */
8
12
  export declare class BaoziWebSocket {
13
+ private config;
9
14
  private orderBookResolvers;
10
15
  private subscriptions;
16
+ constructor(config?: BaoziWebSocketConfig);
11
17
  watchOrderBook(connection: Connection, outcomeId: string): Promise<OrderBook>;
12
18
  private resolveOrderBook;
13
19
  close(connection: Connection): Promise<void>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaoziWebSocket = void 0;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
+ const watch_timeout_1 = require("../../utils/watch-timeout");
5
6
  const utils_1 = require("./utils");
6
7
  /**
7
8
  * Uses Solana's onAccountChange to watch market PDA updates.
@@ -9,8 +10,12 @@ const utils_1 = require("./utils");
9
10
  * and emit a new synthetic order book.
10
11
  */
11
12
  class BaoziWebSocket {
13
+ config;
12
14
  orderBookResolvers = new Map();
13
15
  subscriptions = new Map();
16
+ constructor(config = {}) {
17
+ this.config = config;
18
+ }
14
19
  async watchOrderBook(connection, outcomeId) {
15
20
  const marketPubkey = outcomeId.replace(/-YES$|-NO$|-\d+$/, '');
16
21
  const marketKey = new web3_js_1.PublicKey(marketPubkey);
@@ -45,12 +50,13 @@ class BaoziWebSocket {
45
50
  }, 'confirmed');
46
51
  this.subscriptions.set(marketPubkey, subId);
47
52
  }
48
- return new Promise((resolve, reject) => {
53
+ const dataPromise = new Promise((resolve, reject) => {
49
54
  if (!this.orderBookResolvers.has(marketPubkey)) {
50
55
  this.orderBookResolvers.set(marketPubkey, []);
51
56
  }
52
57
  this.orderBookResolvers.get(marketPubkey).push({ resolve, reject });
53
58
  });
59
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${outcomeId}')`);
54
60
  }
55
61
  resolveOrderBook(marketPubkey, orderBook) {
56
62
  const resolvers = this.orderBookResolvers.get(marketPubkey);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
3
- * Generated at: 2026-05-03T09:02:19.502Z
3
+ * Generated at: 2026-05-03T09:26:01.700Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const kalshiApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.kalshiApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
6
- * Generated at: 2026-05-03T09:02:19.502Z
6
+ * Generated at: 2026-05-03T09:26:01.700Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.kalshiApiSpec = {
@@ -5,6 +5,8 @@ export interface KalshiWebSocketConfig {
5
5
  wsUrl?: string;
6
6
  /** Reconnection interval in milliseconds (default: 5000) */
7
7
  reconnectIntervalMs?: number;
8
+ /** Timeout in ms for watch methods to receive data (default: 30000). 0 = no timeout. */
9
+ watchTimeoutMs?: number;
8
10
  }
9
11
  /**
10
12
  * Kalshi WebSocket implementation for real-time order book and trade streaming.
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.KalshiWebSocket = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
+ const watch_timeout_1 = require("../../utils/watch-timeout");
8
9
  /**
9
10
  * Kalshi WebSocket implementation for real-time order book and trade streaming.
10
11
  * Follows CCXT Pro-style async iterator pattern.
@@ -317,51 +318,68 @@ class KalshiWebSocket {
317
318
  }
318
319
  }
319
320
  async watchOrderBook(ticker) {
320
- // Ensure connection
321
- if (!this.isConnected) {
322
- await this.connect();
321
+ if (this.isTerminated) {
322
+ throw new Error(`WebSocket terminated, cannot watch ${ticker}`);
323
323
  }
324
- // Subscribe if not already subscribed
324
+ // Track the subscription regardless of connection state.
325
+ // When (re)connected, the open handler resubscribes automatically.
325
326
  if (!this.subscribedOrderBookTickers.has(ticker)) {
326
327
  this.subscribedOrderBookTickers.add(ticker);
328
+ }
329
+ // Attempt connection — if it fails, scheduleReconnect handles recovery.
330
+ // The resolver will be fulfilled once the connection is (re)established
331
+ // and data arrives.
332
+ if (!this.isConnected) {
333
+ this.connect().catch(() => {
334
+ // Connection failed — scheduleReconnect is already queued via the
335
+ // close handler, so we intentionally swallow here. The pending
336
+ // resolver will be resolved when data arrives on the next connection.
337
+ });
338
+ }
339
+ else {
340
+ // Already connected — ensure subscription message is sent
327
341
  this.subscribeToOrderbook([ticker]);
328
342
  }
329
343
  // Return a promise that resolves on the next orderbook update
330
- return new Promise((resolve, reject) => {
344
+ const dataPromise = new Promise((resolve, reject) => {
331
345
  if (!this.orderBookResolvers.has(ticker)) {
332
346
  this.orderBookResolvers.set(ticker, []);
333
347
  }
334
348
  this.orderBookResolvers.get(ticker).push({ resolve, reject });
335
349
  });
350
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${ticker}')`);
336
351
  }
337
352
  async watchOrderBooks(tickers) {
338
- // Ensure connection
339
- if (!this.isConnected) {
340
- await this.connect();
353
+ if (this.isTerminated) {
354
+ throw new Error("WebSocket terminated, cannot watch orderbooks");
341
355
  }
342
- // Determine which tickers are actually new (not yet subscribed)
356
+ // Track subscriptions regardless of connection state.
343
357
  const newTickers = tickers.filter((t) => !this.subscribedOrderBookTickers.has(t));
344
- // Add all new tickers to the subscription set
345
358
  for (const t of newTickers) {
346
359
  this.subscribedOrderBookTickers.add(t);
347
360
  }
348
- // Send ONE subscribe message with only the new tickers
349
- if (newTickers.length > 0) {
361
+ // Attempt connection if it fails, scheduleReconnect handles recovery.
362
+ if (!this.isConnected) {
363
+ this.connect().catch(() => {
364
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
365
+ });
366
+ }
367
+ else if (newTickers.length > 0) {
350
368
  this.subscribeToOrderbook(newTickers);
351
369
  }
352
370
  // Wait for all tickers to receive at least one snapshot/update
353
- const entries = await Promise.all(tickers.map((ticker) => new Promise((resolve, reject) => {
371
+ const dataPromise = Promise.all(tickers.map((ticker) => new Promise((resolve, reject) => {
354
372
  if (!this.orderBookResolvers.has(ticker)) {
355
373
  this.orderBookResolvers.set(ticker, []);
356
374
  }
357
375
  this.orderBookResolvers.get(ticker).push({
358
376
  resolve: (book) => {
359
- // Unwrap PromiseLike if needed, but in practice it is always OrderBook
360
377
  Promise.resolve(book).then((b) => resolve([ticker, b]));
361
378
  },
362
379
  reject,
363
380
  });
364
381
  })));
382
+ const entries = await (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBooks(${JSON.stringify(tickers)})`);
365
383
  const result = {};
366
384
  for (const [ticker, book] of entries) {
367
385
  result[ticker] = book;
@@ -369,22 +387,27 @@ class KalshiWebSocket {
369
387
  return result;
370
388
  }
371
389
  async watchTrades(ticker) {
372
- // Ensure connection
373
- if (!this.isConnected) {
374
- await this.connect();
390
+ if (this.isTerminated) {
391
+ throw new Error(`WebSocket terminated, cannot watch trades for ${ticker}`);
375
392
  }
376
- // Subscribe if not already subscribed
377
393
  if (!this.subscribedTradeTickers.has(ticker)) {
378
394
  this.subscribedTradeTickers.add(ticker);
395
+ }
396
+ if (!this.isConnected) {
397
+ this.connect().catch(() => {
398
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
399
+ });
400
+ }
401
+ else {
379
402
  this.subscribeToTrades([ticker]);
380
403
  }
381
- // Return a promise that resolves on the next trade
382
- return new Promise((resolve, reject) => {
404
+ const dataPromise = new Promise((resolve, reject) => {
383
405
  if (!this.tradeResolvers.has(ticker)) {
384
406
  this.tradeResolvers.set(ticker, []);
385
407
  }
386
408
  this.tradeResolvers.get(ticker).push({ resolve, reject });
387
409
  });
410
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${ticker}')`);
388
411
  }
389
412
  async close() {
390
413
  this.isTerminated = true;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
3
- * Generated at: 2026-05-03T09:02:19.560Z
3
+ * Generated at: 2026-05-03T09:26:01.746Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const limitlessApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.limitlessApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
6
- * Generated at: 2026-05-03T09:02:19.560Z
6
+ * Generated at: 2026-05-03T09:26:01.746Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
3
- * Generated at: 2026-05-03T09:02:19.578Z
3
+ * Generated at: 2026-05-03T09:26:01.759Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const myriadApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.myriadApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
6
- * Generated at: 2026-05-03T09:02:19.578Z
6
+ * Generated at: 2026-05-03T09:26:01.759Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.myriadApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
3
- * Generated at: 2026-05-03T09:02:19.583Z
3
+ * Generated at: 2026-05-03T09:26:01.764Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const opinionApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.opinionApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
6
- * Generated at: 2026-05-03T09:02:19.583Z
6
+ * Generated at: 2026-05-03T09:26:01.764Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -2,6 +2,8 @@ import { OrderBook, Trade } from "../../types";
2
2
  export interface OpinionWebSocketConfig {
3
3
  /** Reconnection interval in milliseconds (default: 5000) */
4
4
  reconnectIntervalMs?: number;
5
+ /** Timeout in ms for watch methods to receive data (default: 30000). 0 = no timeout. */
6
+ watchTimeoutMs?: number;
5
7
  }
6
8
  /**
7
9
  * Opinion Trade WebSocket implementation for real-time order book and trade streaming.
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.OpinionWebSocket = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
+ const watch_timeout_1 = require("../../utils/watch-timeout");
8
9
  /**
9
10
  * Opinion Trade WebSocket implementation for real-time order book and trade streaming.
10
11
  * Follows CCXT Pro-style async iterator pattern.
@@ -248,38 +249,54 @@ class OpinionWebSocket {
248
249
  * Returns a promise that resolves on the next orderbook update.
249
250
  */
250
251
  async watchOrderBook(marketId) {
251
- if (!this.isConnected) {
252
- await this.connect();
252
+ if (this.isTerminated) {
253
+ throw new Error(`WebSocket terminated, cannot watch market ${marketId}`);
253
254
  }
254
255
  if (!this.subscribedDepthMarketIds.has(marketId)) {
255
256
  this.subscribedDepthMarketIds.add(marketId);
257
+ }
258
+ if (!this.isConnected) {
259
+ this.connect().catch(() => {
260
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
261
+ });
262
+ }
263
+ else {
256
264
  this.sendSubscribe("market.depth.diff", marketId);
257
265
  }
258
- return new Promise((resolve, reject) => {
266
+ const dataPromise = new Promise((resolve, reject) => {
259
267
  if (!this.orderBookResolvers.has(marketId)) {
260
268
  this.orderBookResolvers.set(marketId, []);
261
269
  }
262
270
  this.orderBookResolvers.get(marketId).push({ resolve, reject });
263
271
  });
272
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${marketId}')`);
264
273
  }
265
274
  /**
266
275
  * Watch trade updates for a given binary market.
267
276
  * Returns a promise that resolves on the next trade.
268
277
  */
269
278
  async watchTrades(marketId) {
270
- if (!this.isConnected) {
271
- await this.connect();
279
+ if (this.isTerminated) {
280
+ throw new Error(`WebSocket terminated, cannot watch trades for market ${marketId}`);
272
281
  }
273
282
  if (!this.subscribedTradeMarketIds.has(marketId)) {
274
283
  this.subscribedTradeMarketIds.add(marketId);
284
+ }
285
+ if (!this.isConnected) {
286
+ this.connect().catch(() => {
287
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
288
+ });
289
+ }
290
+ else {
275
291
  this.sendSubscribe("market.last.trade", marketId);
276
292
  }
277
- return new Promise((resolve, reject) => {
293
+ const dataPromise = new Promise((resolve, reject) => {
278
294
  if (!this.tradeResolvers.has(marketId)) {
279
295
  this.tradeResolvers.set(marketId, []);
280
296
  }
281
297
  this.tradeResolvers.get(marketId).push({ resolve, reject });
282
298
  });
299
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${marketId}')`);
283
300
  }
284
301
  /**
285
302
  * Close the WebSocket connection and reject all pending promises.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
3
- * Generated at: 2026-05-03T09:02:19.515Z
3
+ * Generated at: 2026-05-03T09:26:01.707Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketClobSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketClobSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
6
- * Generated at: 2026-05-03T09:02:19.515Z
6
+ * Generated at: 2026-05-03T09:26:01.707Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketClobSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
3
- * Generated at: 2026-05-03T09:02:19.531Z
3
+ * Generated at: 2026-05-03T09:26:01.724Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketDataSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketDataSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
6
- * Generated at: 2026-05-03T09:02:19.531Z
6
+ * Generated at: 2026-05-03T09:26:01.724Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketDataSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
3
- * Generated at: 2026-05-03T09:02:19.529Z
3
+ * Generated at: 2026-05-03T09:26:01.720Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketGammaSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketGammaSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
6
- * Generated at: 2026-05-03T09:02:19.529Z
6
+ * Generated at: 2026-05-03T09:26:01.720Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketGammaSpec = {
@@ -15,6 +15,8 @@ export interface PolymarketWebSocketConfig {
15
15
  flushIntervalMs?: number;
16
16
  /** Watcher subscription configurations */
17
17
  watcherConfig?: WatcherConfig;
18
+ /** Timeout in ms for watch methods to receive data (default: 30000). 0 = no timeout. */
19
+ watchTimeoutMs?: number;
18
20
  }
19
21
  /**
20
22
  * Wrapper around @nevuamarkets/poly-websockets that provides CCXT Pro-style
@@ -43,6 +43,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.PolymarketWebSocket = void 0;
44
44
  const goldsky_1 = require("../../subscriber/external/goldsky");
45
45
  const watcher_1 = require("../../subscriber/watcher");
46
+ const watch_timeout_1 = require("../../utils/watch-timeout");
46
47
  /**
47
48
  * Wrapper around @nevuamarkets/poly-websockets that provides CCXT Pro-style
48
49
  * watchOrderBook() and watchTrades() methods.
@@ -75,12 +76,13 @@ class PolymarketWebSocket {
75
76
  await this.manager.addSubscriptions([id]);
76
77
  }
77
78
  // Return a promise that resolves on the next orderbook update
78
- return new Promise((resolve, reject) => {
79
+ const dataPromise = new Promise((resolve, reject) => {
79
80
  if (!this.orderBookResolvers.has(id)) {
80
81
  this.orderBookResolvers.set(id, []);
81
82
  }
82
83
  this.orderBookResolvers.get(id).push({ resolve, reject });
83
84
  });
85
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${id}')`);
84
86
  }
85
87
  async unwatchOrderBook(id) {
86
88
  if (!this.manager) {
@@ -108,12 +110,13 @@ class PolymarketWebSocket {
108
110
  await this.manager.addSubscriptions([id]);
109
111
  }
110
112
  // Return a promise that resolves on the next trade
111
- return new Promise((resolve, reject) => {
113
+ const dataPromise = new Promise((resolve, reject) => {
112
114
  if (!this.tradeResolvers.has(id)) {
113
115
  this.tradeResolvers.set(id, []);
114
116
  }
115
117
  this.tradeResolvers.get(id).push({ resolve, reject });
116
118
  });
119
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${id}')`);
117
120
  }
118
121
  async watchAddress(address, types) {
119
122
  return this.watcher.watch(address, types);
@@ -16,16 +16,21 @@
16
16
  import type { PolymarketUS as PolymarketUSClient } from 'polymarket-us';
17
17
  import { OrderBook, Trade } from '../../types';
18
18
  import { PolymarketUSNormalizer } from './normalizer';
19
+ export interface PolymarketUSWebSocketConfig {
20
+ /** Timeout in ms for watch methods to receive data (default: 30000). 0 = no timeout. */
21
+ watchTimeoutMs?: number;
22
+ }
19
23
  export declare class PolymarketUSWebSocket {
20
24
  private readonly client;
21
25
  private readonly normalizer;
26
+ private readonly config;
22
27
  private socket;
23
28
  private initializationPromise?;
24
29
  private readonly bookSubscriptions;
25
30
  private readonly tradeSubscriptions;
26
31
  private readonly orderBookResolvers;
27
32
  private readonly tradeResolvers;
28
- constructor(client: PolymarketUSClient, normalizer: PolymarketUSNormalizer);
33
+ constructor(client: PolymarketUSClient, normalizer: PolymarketUSNormalizer, config?: PolymarketUSWebSocketConfig);
29
34
  watchOrderBook(id: string): Promise<OrderBook>;
30
35
  watchTrades(id: string): Promise<Trade[]>;
31
36
  close(): Promise<void>;
@@ -16,6 +16,7 @@
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.PolymarketUSWebSocket = void 0;
19
+ const watch_timeout_1 = require("../../utils/watch-timeout");
19
20
  const price_1 = require("./price");
20
21
  function sideFromOrderSide(raw) {
21
22
  if (raw === 'ORDER_SIDE_BUY')
@@ -44,15 +45,17 @@ function slugFromId(id) {
44
45
  class PolymarketUSWebSocket {
45
46
  client;
46
47
  normalizer;
48
+ config;
47
49
  socket = null;
48
50
  initializationPromise;
49
51
  bookSubscriptions = new Set();
50
52
  tradeSubscriptions = new Set();
51
53
  orderBookResolvers = new Map();
52
54
  tradeResolvers = new Map();
53
- constructor(client, normalizer) {
55
+ constructor(client, normalizer, config = {}) {
54
56
  this.client = client;
55
57
  this.normalizer = normalizer;
58
+ this.config = config;
56
59
  }
57
60
  async watchOrderBook(id) {
58
61
  const slug = slugFromId(id);
@@ -61,11 +64,12 @@ class PolymarketUSWebSocket {
61
64
  this.bookSubscriptions.add(slug);
62
65
  this.socket.subscribeMarketData(`book:${slug}`, [slug]);
63
66
  }
64
- return new Promise((resolve, reject) => {
67
+ const dataPromise = new Promise((resolve, reject) => {
65
68
  const queue = this.orderBookResolvers.get(slug) ?? [];
66
69
  queue.push({ resolve, reject });
67
70
  this.orderBookResolvers.set(slug, queue);
68
71
  });
72
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${id}')`);
69
73
  }
70
74
  async watchTrades(id) {
71
75
  const slug = slugFromId(id);
@@ -74,11 +78,12 @@ class PolymarketUSWebSocket {
74
78
  this.tradeSubscriptions.add(slug);
75
79
  this.socket.subscribeTrades(`trade:${slug}`, [slug]);
76
80
  }
77
- return new Promise((resolve, reject) => {
81
+ const dataPromise = new Promise((resolve, reject) => {
78
82
  const queue = this.tradeResolvers.get(slug) ?? [];
79
83
  queue.push({ resolve, reject });
80
84
  this.tradeResolvers.set(slug, queue);
81
85
  });
86
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${id}')`);
82
87
  }
83
88
  async close() {
84
89
  if (this.socket) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
3
- * Generated at: 2026-05-03T09:02:19.570Z
3
+ * Generated at: 2026-05-03T09:26:01.752Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const probableApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.probableApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
6
- * Generated at: 2026-05-03T09:02:19.570Z
6
+ * Generated at: 2026-05-03T09:26:01.752Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -6,6 +6,8 @@ export interface ProbableWebSocketConfig {
6
6
  baseUrl?: string;
7
7
  /** Chain ID (default: 56 for BSC mainnet) */
8
8
  chainId?: number;
9
+ /** Timeout in ms for watch methods to receive data (default: 30000). 0 = no timeout. */
10
+ watchTimeoutMs?: number;
9
11
  }
10
12
  /**
11
13
  * Probable WebSocket implementation for real-time order book streaming.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ProbableWebSocket = void 0;
4
+ const watch_timeout_1 = require("../../utils/watch-timeout");
4
5
  /**
5
6
  * Probable WebSocket implementation for real-time order book streaming.
6
7
  * Uses the @prob/clob SDK's subscribePublicStream (no auth required).
@@ -42,12 +43,13 @@ class ProbableWebSocket {
42
43
  this.subscriptions.set(tokenId, sub);
43
44
  }
44
45
  // Return a promise that resolves on the next orderbook update
45
- return new Promise((resolve, reject) => {
46
+ const dataPromise = new Promise((resolve, reject) => {
46
47
  if (!this.orderBookResolvers.has(tokenId)) {
47
48
  this.orderBookResolvers.set(tokenId, []);
48
49
  }
49
50
  this.orderBookResolvers.get(tokenId).push({ resolve, reject });
50
51
  });
52
+ return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${tokenId}')`);
51
53
  }
52
54
  handleOrderBookUpdate(tokenId, data) {
53
55
  const bids = (data.bids || []).map((b) => ({
@@ -34,6 +34,10 @@ function subscriptionKey(msg) {
34
34
  /**
35
35
  * Start a streaming loop for a single-ticker watch method
36
36
  * (watchOrderBook, watchTrades).
37
+ *
38
+ * The exchange layer owns connection lifecycle — watchOrderBook() blocks until
39
+ * data arrives, transparently handling reconnection. This loop is a simple
40
+ * consumer that only terminates on abort or fatal errors.
37
41
  */
38
42
  async function streamSingle(exchange, method, args, id, ws, signal) {
39
43
  const symbol = typeof args[0] === "string" ? args[0] : String(args[0]);
@@ -56,14 +60,18 @@ async function streamSingle(exchange, method, args, id, ws, signal) {
56
60
  : "Unknown streaming error";
57
61
  const code = err instanceof errors_1.BaseError ? err.code : undefined;
58
62
  sendError(ws, id, message, code);
59
- // Brief pause before retry to avoid tight error loops
60
- await new Promise((r) => setTimeout(r, 1000));
63
+ // Fatal error from exchange (terminated, auth failure, etc.) — stop streaming.
64
+ // The exchange layer handles transient reconnection internally and should
65
+ // never throw for recoverable connection drops.
66
+ break;
61
67
  }
62
68
  }
63
69
  }
64
70
  /**
65
71
  * Start a streaming loop for the batch watchOrderBooks method.
66
72
  * Each update is sent as an individual data message per symbol.
73
+ *
74
+ * Same lifecycle contract as streamSingle — the exchange owns reconnection.
67
75
  */
68
76
  async function streamBatch(exchange, method, args, id, ws, signal) {
69
77
  const ids = Array.isArray(args[0]) ? args[0] : [];
@@ -87,7 +95,8 @@ async function streamBatch(exchange, method, args, id, ws, signal) {
87
95
  : "Unknown streaming error";
88
96
  const code = err instanceof errors_1.BaseError ? err.code : undefined;
89
97
  sendError(ws, id, message, code);
90
- await new Promise((r) => setTimeout(r, 1000));
98
+ // Fatal error stop streaming. Exchange handles transient reconnection.
99
+ break;
91
100
  }
92
101
  }
93
102
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Default timeout for watch methods (30 seconds).
3
+ *
4
+ * Generous enough for valid but illiquid markets, but prevents
5
+ * hanging forever on non-existing IDs.
6
+ */
7
+ export declare const DEFAULT_WATCH_TIMEOUT_MS = 30000;
8
+ /**
9
+ * Wrap a watch promise with a timeout that rejects if no data arrives.
10
+ *
11
+ * Used by all exchange WebSocket implementations to prevent indefinite
12
+ * hangs when subscribing to non-existing market IDs.
13
+ *
14
+ * @param promise - The data promise (resolves when WS data arrives)
15
+ * @param timeoutMs - Maximum time to wait in milliseconds (0 = no timeout)
16
+ * @param label - Human-readable label for the error message
17
+ */
18
+ export declare function withWatchTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_WATCH_TIMEOUT_MS = void 0;
4
+ exports.withWatchTimeout = withWatchTimeout;
5
+ /**
6
+ * Default timeout for watch methods (30 seconds).
7
+ *
8
+ * Generous enough for valid but illiquid markets, but prevents
9
+ * hanging forever on non-existing IDs.
10
+ */
11
+ exports.DEFAULT_WATCH_TIMEOUT_MS = 30_000;
12
+ /**
13
+ * Wrap a watch promise with a timeout that rejects if no data arrives.
14
+ *
15
+ * Used by all exchange WebSocket implementations to prevent indefinite
16
+ * hangs when subscribing to non-existing market IDs.
17
+ *
18
+ * @param promise - The data promise (resolves when WS data arrives)
19
+ * @param timeoutMs - Maximum time to wait in milliseconds (0 = no timeout)
20
+ * @param label - Human-readable label for the error message
21
+ */
22
+ function withWatchTimeout(promise, timeoutMs, label) {
23
+ if (timeoutMs <= 0)
24
+ return promise;
25
+ let timer;
26
+ const timeoutPromise = new Promise((_, reject) => {
27
+ timer = setTimeout(() => {
28
+ reject(new Error(`${label}: timed out after ${timeoutMs}ms waiting for data. ` +
29
+ `The ID may not exist on this exchange.`));
30
+ }, timeoutMs);
31
+ });
32
+ return Promise.race([promise, timeoutPromise]).finally(() => {
33
+ clearTimeout(timer);
34
+ });
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.37.0",
3
+ "version": "2.37.2",
4
4
  "description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,8 +29,8 @@
29
29
  "test": "jest -c jest.config.js",
30
30
  "server": "tsx watch src/server/index.ts",
31
31
  "server:prod": "node dist/server/index.js",
32
- "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.37.0,library=urllib3",
33
- "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.37.0,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
32
+ "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.37.2,library=urllib3",
33
+ "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.37.2,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
34
34
  "fetch:openapi": "node scripts/fetch-openapi-specs.js",
35
35
  "extract:jsdoc": "node ../scripts/extract-jsdoc.js",
36
36
  "generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",