pmxt-core 2.36.1 → 2.37.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.
@@ -460,10 +460,14 @@ export declare abstract class PredictionMarketExchange {
460
460
  * Fetch the current order book (bids/asks) for a specific outcome.
461
461
  * Essential for calculating spread, depth, and execution prices.
462
462
  *
463
- * @param id - The Outcome ID (outcomeId)
463
+ * @param id - The Outcome ID (outcomeId) or market slug
464
+ * @param side - Optional 'yes' or 'no' to explicitly indicate the
465
+ * outcome side. Required for exchanges where the API returns a
466
+ * single orderbook per market (e.g. Limitless) and the caller
467
+ * passes a slug instead of a token ID.
464
468
  * @returns Current order book with bids and asks
465
469
  */
466
- fetchOrderBook(id: string): Promise<OrderBook>;
470
+ fetchOrderBook(id: string, side?: 'yes' | 'no'): Promise<OrderBook>;
467
471
  /**
468
472
  * Fetch raw trade history for a specific outcome.
469
473
  *
@@ -406,10 +406,14 @@ class PredictionMarketExchange {
406
406
  * Fetch the current order book (bids/asks) for a specific outcome.
407
407
  * Essential for calculating spread, depth, and execution prices.
408
408
  *
409
- * @param id - The Outcome ID (outcomeId)
409
+ * @param id - The Outcome ID (outcomeId) or market slug
410
+ * @param side - Optional 'yes' or 'no' to explicitly indicate the
411
+ * outcome side. Required for exchanges where the API returns a
412
+ * single orderbook per market (e.g. Limitless) and the caller
413
+ * passes a slug instead of a token ID.
410
414
  * @returns Current order book with bids and asks
411
415
  */
412
- async fetchOrderBook(id) {
416
+ async fetchOrderBook(id, side) {
413
417
  throw new Error("Method fetchOrderBook not implemented.");
414
418
  }
415
419
  /**
@@ -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-03T08:52:44.008Z
3
+ * Generated at: 2026-05-03T09:04:43.064Z
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-03T08:52:44.008Z
6
+ * Generated at: 2026-05-03T09:04:43.064Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.kalshiApiSpec = {
@@ -317,13 +317,26 @@ class KalshiWebSocket {
317
317
  }
318
318
  }
319
319
  async watchOrderBook(ticker) {
320
- // Ensure connection
321
- if (!this.isConnected) {
322
- await this.connect();
320
+ if (this.isTerminated) {
321
+ throw new Error(`WebSocket terminated, cannot watch ${ticker}`);
323
322
  }
324
- // Subscribe if not already subscribed
323
+ // Track the subscription regardless of connection state.
324
+ // When (re)connected, the open handler resubscribes automatically.
325
325
  if (!this.subscribedOrderBookTickers.has(ticker)) {
326
326
  this.subscribedOrderBookTickers.add(ticker);
327
+ }
328
+ // Attempt connection — if it fails, scheduleReconnect handles recovery.
329
+ // The resolver will be fulfilled once the connection is (re)established
330
+ // and data arrives.
331
+ if (!this.isConnected) {
332
+ this.connect().catch(() => {
333
+ // Connection failed — scheduleReconnect is already queued via the
334
+ // close handler, so we intentionally swallow here. The pending
335
+ // resolver will be resolved when data arrives on the next connection.
336
+ });
337
+ }
338
+ else {
339
+ // Already connected — ensure subscription message is sent
327
340
  this.subscribeToOrderbook([ticker]);
328
341
  }
329
342
  // Return a promise that resolves on the next orderbook update
@@ -335,18 +348,21 @@ class KalshiWebSocket {
335
348
  });
336
349
  }
337
350
  async watchOrderBooks(tickers) {
338
- // Ensure connection
339
- if (!this.isConnected) {
340
- await this.connect();
351
+ if (this.isTerminated) {
352
+ throw new Error("WebSocket terminated, cannot watch orderbooks");
341
353
  }
342
- // Determine which tickers are actually new (not yet subscribed)
354
+ // Track subscriptions regardless of connection state.
343
355
  const newTickers = tickers.filter((t) => !this.subscribedOrderBookTickers.has(t));
344
- // Add all new tickers to the subscription set
345
356
  for (const t of newTickers) {
346
357
  this.subscribedOrderBookTickers.add(t);
347
358
  }
348
- // Send ONE subscribe message with only the new tickers
349
- if (newTickers.length > 0) {
359
+ // Attempt connection if it fails, scheduleReconnect handles recovery.
360
+ if (!this.isConnected) {
361
+ this.connect().catch(() => {
362
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
363
+ });
364
+ }
365
+ else if (newTickers.length > 0) {
350
366
  this.subscribeToOrderbook(newTickers);
351
367
  }
352
368
  // Wait for all tickers to receive at least one snapshot/update
@@ -356,7 +372,6 @@ class KalshiWebSocket {
356
372
  }
357
373
  this.orderBookResolvers.get(ticker).push({
358
374
  resolve: (book) => {
359
- // Unwrap PromiseLike if needed, but in practice it is always OrderBook
360
375
  Promise.resolve(book).then((b) => resolve([ticker, b]));
361
376
  },
362
377
  reject,
@@ -369,16 +384,20 @@ class KalshiWebSocket {
369
384
  return result;
370
385
  }
371
386
  async watchTrades(ticker) {
372
- // Ensure connection
373
- if (!this.isConnected) {
374
- await this.connect();
387
+ if (this.isTerminated) {
388
+ throw new Error(`WebSocket terminated, cannot watch trades for ${ticker}`);
375
389
  }
376
- // Subscribe if not already subscribed
377
390
  if (!this.subscribedTradeTickers.has(ticker)) {
378
391
  this.subscribedTradeTickers.add(ticker);
392
+ }
393
+ if (!this.isConnected) {
394
+ this.connect().catch(() => {
395
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
396
+ });
397
+ }
398
+ else {
379
399
  this.subscribeToTrades([ticker]);
380
400
  }
381
- // Return a promise that resolves on the next trade
382
401
  return new Promise((resolve, reject) => {
383
402
  if (!this.tradeResolvers.has(ticker)) {
384
403
  this.tradeResolvers.set(ticker, []);
@@ -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-03T08:52:44.066Z
3
+ * Generated at: 2026-05-03T09:04:43.109Z
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-03T08:52:44.066Z
6
+ * Generated at: 2026-05-03T09:04:43.109Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -28,7 +28,7 @@ export declare class LimitlessExchange extends PredictionMarketExchange {
28
28
  protected fetchMarketsImpl(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
29
29
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
30
30
  fetchOHLCV(id: string, params: OHLCVParams): Promise<PriceCandle[]>;
31
- fetchOrderBook(id: string): Promise<OrderBook>;
31
+ fetchOrderBook(id: string, side?: 'yes' | 'no'): Promise<OrderBook>;
32
32
  private isNoOutcome;
33
33
  fetchTrades(id: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]>;
34
34
  fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]>;
@@ -129,14 +129,14 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
129
129
  const rawPrices = await this.fetcher.fetchRawOHLCV(slug, params);
130
130
  return this.normalizer.normalizeOHLCV(rawPrices, params);
131
131
  }
132
- async fetchOrderBook(id) {
132
+ async fetchOrderBook(id, side) {
133
133
  const slug = await this.resolveSlug(id);
134
134
  const rawOrderBook = await this.fetcher.fetchRawOrderBook(slug);
135
135
  const orderBook = this.normalizer.normalizeOrderBook(rawOrderBook, id);
136
136
  // The Limitless API always returns the Yes-side order book regardless
137
137
  // of which token is queried. If the caller asked for the No token,
138
138
  // flip: noBid = 1 - yesAsk, noAsk = 1 - yesBid.
139
- const isNoToken = await this.isNoOutcome(id, slug);
139
+ const isNoToken = side === 'no' || (!side && await this.isNoOutcome(id, slug));
140
140
  if (isNoToken) {
141
141
  return {
142
142
  bids: orderBook.asks.map((level) => ({ price: 1 - level.price, size: level.size }))
@@ -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-03T08:52:44.082Z
3
+ * Generated at: 2026-05-03T09:04:43.127Z
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-03T08:52:44.082Z
6
+ * Generated at: 2026-05-03T09:04:43.127Z
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-03T08:52:44.089Z
3
+ * Generated at: 2026-05-03T09:04:43.132Z
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-03T08:52:44.089Z
6
+ * Generated at: 2026-05-03T09:04:43.132Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -248,11 +248,18 @@ class OpinionWebSocket {
248
248
  * Returns a promise that resolves on the next orderbook update.
249
249
  */
250
250
  async watchOrderBook(marketId) {
251
- if (!this.isConnected) {
252
- await this.connect();
251
+ if (this.isTerminated) {
252
+ throw new Error(`WebSocket terminated, cannot watch market ${marketId}`);
253
253
  }
254
254
  if (!this.subscribedDepthMarketIds.has(marketId)) {
255
255
  this.subscribedDepthMarketIds.add(marketId);
256
+ }
257
+ if (!this.isConnected) {
258
+ this.connect().catch(() => {
259
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
260
+ });
261
+ }
262
+ else {
256
263
  this.sendSubscribe("market.depth.diff", marketId);
257
264
  }
258
265
  return new Promise((resolve, reject) => {
@@ -267,11 +274,18 @@ class OpinionWebSocket {
267
274
  * Returns a promise that resolves on the next trade.
268
275
  */
269
276
  async watchTrades(marketId) {
270
- if (!this.isConnected) {
271
- await this.connect();
277
+ if (this.isTerminated) {
278
+ throw new Error(`WebSocket terminated, cannot watch trades for market ${marketId}`);
272
279
  }
273
280
  if (!this.subscribedTradeMarketIds.has(marketId)) {
274
281
  this.subscribedTradeMarketIds.add(marketId);
282
+ }
283
+ if (!this.isConnected) {
284
+ this.connect().catch(() => {
285
+ // Swallow — scheduleReconnect will retry. Resolvers stay pending.
286
+ });
287
+ }
288
+ else {
275
289
  this.sendSubscribe("market.last.trade", marketId);
276
290
  }
277
291
  return new Promise((resolve, reject) => {
@@ -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-03T08:52:44.016Z
3
+ * Generated at: 2026-05-03T09:04:43.075Z
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-03T08:52:44.016Z
6
+ * Generated at: 2026-05-03T09:04:43.075Z
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-03T08:52:44.040Z
3
+ * Generated at: 2026-05-03T09:04:43.088Z
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-03T08:52:44.040Z
6
+ * Generated at: 2026-05-03T09:04:43.088Z
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-03T08:52:44.035Z
3
+ * Generated at: 2026-05-03T09:04:43.086Z
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-03T08:52:44.035Z
6
+ * Generated at: 2026-05-03T09:04:43.086Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketGammaSpec = {
@@ -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-03T08:52:44.075Z
3
+ * Generated at: 2026-05-03T09:04:43.115Z
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-03T08:52:44.075Z
6
+ * Generated at: 2026-05-03T09:04:43.115Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -85,12 +85,17 @@
85
85
  ]
86
86
  },
87
87
  "fetchOrderBook": {
88
- "verb": "get",
88
+ "verb": "post",
89
89
  "args": [
90
90
  {
91
91
  "name": "id",
92
92
  "kind": "string",
93
93
  "optional": false
94
+ },
95
+ {
96
+ "name": "side",
97
+ "kind": "unknown",
98
+ "optional": true
94
99
  }
95
100
  ]
96
101
  },
@@ -630,16 +630,33 @@ paths:
630
630
  $ref: '#/components/schemas/PriceCandle'
631
631
  description: Fetch historical OHLCV (candlestick) price data for a specific market outcome.
632
632
  '/api/{exchange}/fetchOrderBook':
633
- get:
633
+ post:
634
634
  summary: Fetch Order Book
635
635
  operationId: fetchOrderBook
636
636
  parameters:
637
637
  - $ref: '#/components/parameters/ExchangeParam'
638
- - in: query
639
- name: id
640
- required: true
641
- schema:
642
- type: string
638
+ requestBody:
639
+ content:
640
+ application/json:
641
+ schema:
642
+ title: FetchOrderBookRequest
643
+ type: object
644
+ properties:
645
+ args:
646
+ type: array
647
+ minItems: 1
648
+ maxItems: 2
649
+ items:
650
+ oneOf:
651
+ - type: string
652
+ - type: string
653
+ enum:
654
+ - 'yes'
655
+ - 'no'
656
+ credentials:
657
+ $ref: '#/components/schemas/ExchangeCredentials'
658
+ required:
659
+ - args
643
660
  responses:
644
661
  '200':
645
662
  description: Fetch Order Book response
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.36.1",
3
+ "version": "2.37.1",
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.36.1,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.36.1,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.1,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.1,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",