pmxtjs 2.46.3 → 2.46.5
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/dist/esm/pmxt/client.d.ts +4 -1
- package/dist/esm/pmxt/client.js +17 -2
- package/dist/esm/pmxt/ws-client.d.ts +4 -1
- package/dist/esm/pmxt/ws-client.js +38 -20
- package/dist/pmxt/client.d.ts +4 -1
- package/dist/pmxt/client.js +17 -2
- package/dist/pmxt/ws-client.d.ts +4 -1
- package/dist/pmxt/ws-client.js +38 -20
- package/generated/package.json +1 -1
- package/package.json +2 -2
- package/pmxt/client.ts +20 -2
- package/pmxt/ws-client.ts +41 -20
|
@@ -57,6 +57,7 @@ export interface ExchangeOptions {
|
|
|
57
57
|
* prediction market platforms (Polymarket, Kalshi, etc.).
|
|
58
58
|
*/
|
|
59
59
|
export declare abstract class Exchange {
|
|
60
|
+
private static readonly OBDATA_WATCH_ALL_SOURCES;
|
|
60
61
|
protected exchangeName: string;
|
|
61
62
|
protected apiKey?: string;
|
|
62
63
|
protected privateKey?: string;
|
|
@@ -116,6 +117,7 @@ export declare abstract class Exchange {
|
|
|
116
117
|
private watchViaWs;
|
|
117
118
|
private wsTransportUnavailableError;
|
|
118
119
|
private isWsTransportUnavailableError;
|
|
120
|
+
private defaultWatchAllOrderBookVenues;
|
|
119
121
|
private getWsInternals;
|
|
120
122
|
private wsSubscriptionKey;
|
|
121
123
|
private getWsSubscriptionId;
|
|
@@ -282,7 +284,8 @@ export declare abstract class Exchange {
|
|
|
282
284
|
* Call repeatedly in a loop to stream updates (CCXT Pro pattern).
|
|
283
285
|
* Requires hosted mode (`pmxtApiKey` set).
|
|
284
286
|
*
|
|
285
|
-
* @param venues - Optional venue filter
|
|
287
|
+
* @param venues - Optional venue filter. Defaults to this exchange's venue
|
|
288
|
+
* for venue clients (e.g. Kalshi -> ["kalshi"]); Router defaults to all venues.
|
|
286
289
|
* @returns Next event with source, symbol, and orderbook
|
|
287
290
|
*
|
|
288
291
|
* @example
|
package/dist/esm/pmxt/client.js
CHANGED
|
@@ -126,6 +126,12 @@ function convertSubscriptionSnapshot(raw) {
|
|
|
126
126
|
* prediction market platforms (Polymarket, Kalshi, etc.).
|
|
127
127
|
*/
|
|
128
128
|
export class Exchange {
|
|
129
|
+
static OBDATA_WATCH_ALL_SOURCES = new Set([
|
|
130
|
+
"polymarket",
|
|
131
|
+
"limitless",
|
|
132
|
+
"kalshi",
|
|
133
|
+
"opinion",
|
|
134
|
+
]);
|
|
129
135
|
exchangeName;
|
|
130
136
|
apiKey;
|
|
131
137
|
privateKey;
|
|
@@ -358,6 +364,12 @@ export class Exchange {
|
|
|
358
364
|
return error instanceof PmxtError
|
|
359
365
|
&& /connection failed|no websocket|websocket.*not connected/i.test(error.message);
|
|
360
366
|
}
|
|
367
|
+
defaultWatchAllOrderBookVenues() {
|
|
368
|
+
if (Exchange.OBDATA_WATCH_ALL_SOURCES.has(this.exchangeName)) {
|
|
369
|
+
return [this.exchangeName];
|
|
370
|
+
}
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
361
373
|
getWsInternals(ws) {
|
|
362
374
|
return ws;
|
|
363
375
|
}
|
|
@@ -384,6 +396,7 @@ export class Exchange {
|
|
|
384
396
|
}
|
|
385
397
|
internals.activeSubs.delete(subKey);
|
|
386
398
|
internals.subscriptions.delete(requestId);
|
|
399
|
+
internals.dataQueues.delete(requestId);
|
|
387
400
|
internals.dataStore.delete(requestId);
|
|
388
401
|
const firstArg = args[0] ?? "";
|
|
389
402
|
const symbols = Array.isArray(firstArg)
|
|
@@ -1563,7 +1576,8 @@ export class Exchange {
|
|
|
1563
1576
|
* Call repeatedly in a loop to stream updates (CCXT Pro pattern).
|
|
1564
1577
|
* Requires hosted mode (`pmxtApiKey` set).
|
|
1565
1578
|
*
|
|
1566
|
-
* @param venues - Optional venue filter
|
|
1579
|
+
* @param venues - Optional venue filter. Defaults to this exchange's venue
|
|
1580
|
+
* for venue clients (e.g. Kalshi -> ["kalshi"]); Router defaults to all venues.
|
|
1567
1581
|
* @returns Next event with source, symbol, and orderbook
|
|
1568
1582
|
*
|
|
1569
1583
|
* @example
|
|
@@ -1580,7 +1594,8 @@ export class Exchange {
|
|
|
1580
1594
|
if (!this.isHosted) {
|
|
1581
1595
|
throw new PmxtError("watchAllOrderBooks() requires hosted mode (set pmxtApiKey)");
|
|
1582
1596
|
}
|
|
1583
|
-
const
|
|
1597
|
+
const effectiveVenues = venues ?? this.defaultWatchAllOrderBookVenues();
|
|
1598
|
+
const args = effectiveVenues?.length ? [effectiveVenues] : [];
|
|
1584
1599
|
const wsData = await this.watchViaWs("watchAllOrderBooks", args);
|
|
1585
1600
|
if (wsData !== null) {
|
|
1586
1601
|
return {
|
|
@@ -18,7 +18,9 @@ export declare class SidecarWsClient {
|
|
|
18
18
|
private accessToken;
|
|
19
19
|
private authParamName;
|
|
20
20
|
private closed;
|
|
21
|
-
/** requestId ->
|
|
21
|
+
/** requestId -> queued data payloads for single-event watch methods */
|
|
22
|
+
private dataQueues;
|
|
23
|
+
/** requestId[:symbol] -> latest data payload for batch snapshots */
|
|
22
24
|
private dataStore;
|
|
23
25
|
/** requestId -> subscription metadata */
|
|
24
26
|
private subscriptions;
|
|
@@ -30,6 +32,7 @@ export declare class SidecarWsClient {
|
|
|
30
32
|
private connect;
|
|
31
33
|
private getWebSocketConstructor;
|
|
32
34
|
private dispatch;
|
|
35
|
+
private deliverOrQueue;
|
|
33
36
|
subscribe(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<any>;
|
|
34
37
|
subscribeBatch(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<Record<string, any>>;
|
|
35
38
|
close(): void;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* does not support the /ws endpoint.
|
|
8
8
|
*/
|
|
9
9
|
import { PmxtError } from "./errors.js";
|
|
10
|
+
const MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION = 100_000;
|
|
10
11
|
/**
|
|
11
12
|
* Multiplexed WebSocket client for the pmxt sidecar.
|
|
12
13
|
*
|
|
@@ -19,7 +20,9 @@ export class SidecarWsClient {
|
|
|
19
20
|
accessToken;
|
|
20
21
|
authParamName;
|
|
21
22
|
closed = false;
|
|
22
|
-
/** requestId ->
|
|
23
|
+
/** requestId -> queued data payloads for single-event watch methods */
|
|
24
|
+
dataQueues = new Map();
|
|
25
|
+
/** requestId[:symbol] -> latest data payload for batch snapshots */
|
|
23
26
|
dataStore = new Map();
|
|
24
27
|
/** requestId -> subscription metadata */
|
|
25
28
|
subscriptions = new Map();
|
|
@@ -154,16 +157,27 @@ export class SidecarWsClient {
|
|
|
154
157
|
if (eventType === "data" && requestId) {
|
|
155
158
|
const symbol = msg.symbol || "";
|
|
156
159
|
const data = msg.data || {};
|
|
157
|
-
// Store by (requestId:symbol) for batch methods
|
|
160
|
+
// Store by (requestId:symbol) for batch methods.
|
|
158
161
|
this.dataStore.set(`${requestId}:${symbol}`, data);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
this.deliverOrQueue(requestId, data);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
deliverOrQueue(requestId, data) {
|
|
166
|
+
const sub = this.subscriptions.get(requestId);
|
|
167
|
+
if (sub?.resolve) {
|
|
168
|
+
sub.resolve(data);
|
|
169
|
+
sub.resolve = null;
|
|
170
|
+
sub.reject = null;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let queue = this.dataQueues.get(requestId);
|
|
174
|
+
if (!queue) {
|
|
175
|
+
queue = [];
|
|
176
|
+
this.dataQueues.set(requestId, queue);
|
|
177
|
+
}
|
|
178
|
+
queue.push(data);
|
|
179
|
+
if (queue.length > MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION) {
|
|
180
|
+
queue.splice(0, queue.length - MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION);
|
|
167
181
|
}
|
|
168
182
|
}
|
|
169
183
|
// ------------------------------------------------------------------
|
|
@@ -233,8 +247,8 @@ export class SidecarWsClient {
|
|
|
233
247
|
throw new PmxtError('[ws-client] Cannot send: WebSocket not connected');
|
|
234
248
|
}
|
|
235
249
|
this.ws.send(JSON.stringify(message));
|
|
236
|
-
// Wait for first data event
|
|
237
|
-
await this.waitForData(requestId, timeoutMs);
|
|
250
|
+
// Wait for first data event.
|
|
251
|
+
const firstData = await this.waitForData(requestId, timeoutMs);
|
|
238
252
|
// Collect per-symbol data
|
|
239
253
|
const result = {};
|
|
240
254
|
for (const symbol of symbols) {
|
|
@@ -246,9 +260,8 @@ export class SidecarWsClient {
|
|
|
246
260
|
}
|
|
247
261
|
// If no per-symbol data, return the single data event as-is
|
|
248
262
|
if (Object.keys(result).length === 0) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return data;
|
|
263
|
+
if (firstData && typeof firstData === "object") {
|
|
264
|
+
return firstData;
|
|
252
265
|
}
|
|
253
266
|
}
|
|
254
267
|
return result;
|
|
@@ -264,6 +277,8 @@ export class SidecarWsClient {
|
|
|
264
277
|
}
|
|
265
278
|
this.ws = null;
|
|
266
279
|
}
|
|
280
|
+
this.dataQueues.clear();
|
|
281
|
+
this.dataStore.clear();
|
|
267
282
|
}
|
|
268
283
|
get connected() {
|
|
269
284
|
return this.ws !== null && !this.closed;
|
|
@@ -272,11 +287,14 @@ export class SidecarWsClient {
|
|
|
272
287
|
// Internal
|
|
273
288
|
// ------------------------------------------------------------------
|
|
274
289
|
waitForData(requestId, timeoutMs) {
|
|
275
|
-
// Check if data is already available
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
290
|
+
// Check if queued data is already available.
|
|
291
|
+
const queue = this.dataQueues.get(requestId);
|
|
292
|
+
if (queue && queue.length > 0) {
|
|
293
|
+
const next = queue.shift();
|
|
294
|
+
if (queue.length === 0) {
|
|
295
|
+
this.dataQueues.delete(requestId);
|
|
296
|
+
}
|
|
297
|
+
return Promise.resolve(next);
|
|
280
298
|
}
|
|
281
299
|
return new Promise((resolve, reject) => {
|
|
282
300
|
const sub = this.subscriptions.get(requestId);
|
package/dist/pmxt/client.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface ExchangeOptions {
|
|
|
57
57
|
* prediction market platforms (Polymarket, Kalshi, etc.).
|
|
58
58
|
*/
|
|
59
59
|
export declare abstract class Exchange {
|
|
60
|
+
private static readonly OBDATA_WATCH_ALL_SOURCES;
|
|
60
61
|
protected exchangeName: string;
|
|
61
62
|
protected apiKey?: string;
|
|
62
63
|
protected privateKey?: string;
|
|
@@ -116,6 +117,7 @@ export declare abstract class Exchange {
|
|
|
116
117
|
private watchViaWs;
|
|
117
118
|
private wsTransportUnavailableError;
|
|
118
119
|
private isWsTransportUnavailableError;
|
|
120
|
+
private defaultWatchAllOrderBookVenues;
|
|
119
121
|
private getWsInternals;
|
|
120
122
|
private wsSubscriptionKey;
|
|
121
123
|
private getWsSubscriptionId;
|
|
@@ -282,7 +284,8 @@ export declare abstract class Exchange {
|
|
|
282
284
|
* Call repeatedly in a loop to stream updates (CCXT Pro pattern).
|
|
283
285
|
* Requires hosted mode (`pmxtApiKey` set).
|
|
284
286
|
*
|
|
285
|
-
* @param venues - Optional venue filter
|
|
287
|
+
* @param venues - Optional venue filter. Defaults to this exchange's venue
|
|
288
|
+
* for venue clients (e.g. Kalshi -> ["kalshi"]); Router defaults to all venues.
|
|
286
289
|
* @returns Next event with source, symbol, and orderbook
|
|
287
290
|
*
|
|
288
291
|
* @example
|
package/dist/pmxt/client.js
CHANGED
|
@@ -129,6 +129,12 @@ function convertSubscriptionSnapshot(raw) {
|
|
|
129
129
|
* prediction market platforms (Polymarket, Kalshi, etc.).
|
|
130
130
|
*/
|
|
131
131
|
class Exchange {
|
|
132
|
+
static OBDATA_WATCH_ALL_SOURCES = new Set([
|
|
133
|
+
"polymarket",
|
|
134
|
+
"limitless",
|
|
135
|
+
"kalshi",
|
|
136
|
+
"opinion",
|
|
137
|
+
]);
|
|
132
138
|
exchangeName;
|
|
133
139
|
apiKey;
|
|
134
140
|
privateKey;
|
|
@@ -361,6 +367,12 @@ class Exchange {
|
|
|
361
367
|
return error instanceof errors_js_1.PmxtError
|
|
362
368
|
&& /connection failed|no websocket|websocket.*not connected/i.test(error.message);
|
|
363
369
|
}
|
|
370
|
+
defaultWatchAllOrderBookVenues() {
|
|
371
|
+
if (Exchange.OBDATA_WATCH_ALL_SOURCES.has(this.exchangeName)) {
|
|
372
|
+
return [this.exchangeName];
|
|
373
|
+
}
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
364
376
|
getWsInternals(ws) {
|
|
365
377
|
return ws;
|
|
366
378
|
}
|
|
@@ -387,6 +399,7 @@ class Exchange {
|
|
|
387
399
|
}
|
|
388
400
|
internals.activeSubs.delete(subKey);
|
|
389
401
|
internals.subscriptions.delete(requestId);
|
|
402
|
+
internals.dataQueues.delete(requestId);
|
|
390
403
|
internals.dataStore.delete(requestId);
|
|
391
404
|
const firstArg = args[0] ?? "";
|
|
392
405
|
const symbols = Array.isArray(firstArg)
|
|
@@ -1566,7 +1579,8 @@ class Exchange {
|
|
|
1566
1579
|
* Call repeatedly in a loop to stream updates (CCXT Pro pattern).
|
|
1567
1580
|
* Requires hosted mode (`pmxtApiKey` set).
|
|
1568
1581
|
*
|
|
1569
|
-
* @param venues - Optional venue filter
|
|
1582
|
+
* @param venues - Optional venue filter. Defaults to this exchange's venue
|
|
1583
|
+
* for venue clients (e.g. Kalshi -> ["kalshi"]); Router defaults to all venues.
|
|
1570
1584
|
* @returns Next event with source, symbol, and orderbook
|
|
1571
1585
|
*
|
|
1572
1586
|
* @example
|
|
@@ -1583,7 +1597,8 @@ class Exchange {
|
|
|
1583
1597
|
if (!this.isHosted) {
|
|
1584
1598
|
throw new errors_js_1.PmxtError("watchAllOrderBooks() requires hosted mode (set pmxtApiKey)");
|
|
1585
1599
|
}
|
|
1586
|
-
const
|
|
1600
|
+
const effectiveVenues = venues ?? this.defaultWatchAllOrderBookVenues();
|
|
1601
|
+
const args = effectiveVenues?.length ? [effectiveVenues] : [];
|
|
1587
1602
|
const wsData = await this.watchViaWs("watchAllOrderBooks", args);
|
|
1588
1603
|
if (wsData !== null) {
|
|
1589
1604
|
return {
|
package/dist/pmxt/ws-client.d.ts
CHANGED
|
@@ -18,7 +18,9 @@ export declare class SidecarWsClient {
|
|
|
18
18
|
private accessToken;
|
|
19
19
|
private authParamName;
|
|
20
20
|
private closed;
|
|
21
|
-
/** requestId ->
|
|
21
|
+
/** requestId -> queued data payloads for single-event watch methods */
|
|
22
|
+
private dataQueues;
|
|
23
|
+
/** requestId[:symbol] -> latest data payload for batch snapshots */
|
|
22
24
|
private dataStore;
|
|
23
25
|
/** requestId -> subscription metadata */
|
|
24
26
|
private subscriptions;
|
|
@@ -30,6 +32,7 @@ export declare class SidecarWsClient {
|
|
|
30
32
|
private connect;
|
|
31
33
|
private getWebSocketConstructor;
|
|
32
34
|
private dispatch;
|
|
35
|
+
private deliverOrQueue;
|
|
33
36
|
subscribe(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<any>;
|
|
34
37
|
subscribeBatch(exchange: string, method: string, args: any[], credentials?: Record<string, any>, timeoutMs?: number): Promise<Record<string, any>>;
|
|
35
38
|
close(): void;
|
package/dist/pmxt/ws-client.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.SidecarWsClient = void 0;
|
|
12
12
|
const errors_js_1 = require("./errors.js");
|
|
13
|
+
const MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION = 100_000;
|
|
13
14
|
/**
|
|
14
15
|
* Multiplexed WebSocket client for the pmxt sidecar.
|
|
15
16
|
*
|
|
@@ -22,7 +23,9 @@ class SidecarWsClient {
|
|
|
22
23
|
accessToken;
|
|
23
24
|
authParamName;
|
|
24
25
|
closed = false;
|
|
25
|
-
/** requestId ->
|
|
26
|
+
/** requestId -> queued data payloads for single-event watch methods */
|
|
27
|
+
dataQueues = new Map();
|
|
28
|
+
/** requestId[:symbol] -> latest data payload for batch snapshots */
|
|
26
29
|
dataStore = new Map();
|
|
27
30
|
/** requestId -> subscription metadata */
|
|
28
31
|
subscriptions = new Map();
|
|
@@ -157,16 +160,27 @@ class SidecarWsClient {
|
|
|
157
160
|
if (eventType === "data" && requestId) {
|
|
158
161
|
const symbol = msg.symbol || "";
|
|
159
162
|
const data = msg.data || {};
|
|
160
|
-
// Store by (requestId:symbol) for batch methods
|
|
163
|
+
// Store by (requestId:symbol) for batch methods.
|
|
161
164
|
this.dataStore.set(`${requestId}:${symbol}`, data);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
this.deliverOrQueue(requestId, data);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
deliverOrQueue(requestId, data) {
|
|
169
|
+
const sub = this.subscriptions.get(requestId);
|
|
170
|
+
if (sub?.resolve) {
|
|
171
|
+
sub.resolve(data);
|
|
172
|
+
sub.resolve = null;
|
|
173
|
+
sub.reject = null;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
let queue = this.dataQueues.get(requestId);
|
|
177
|
+
if (!queue) {
|
|
178
|
+
queue = [];
|
|
179
|
+
this.dataQueues.set(requestId, queue);
|
|
180
|
+
}
|
|
181
|
+
queue.push(data);
|
|
182
|
+
if (queue.length > MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION) {
|
|
183
|
+
queue.splice(0, queue.length - MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION);
|
|
170
184
|
}
|
|
171
185
|
}
|
|
172
186
|
// ------------------------------------------------------------------
|
|
@@ -236,8 +250,8 @@ class SidecarWsClient {
|
|
|
236
250
|
throw new errors_js_1.PmxtError('[ws-client] Cannot send: WebSocket not connected');
|
|
237
251
|
}
|
|
238
252
|
this.ws.send(JSON.stringify(message));
|
|
239
|
-
// Wait for first data event
|
|
240
|
-
await this.waitForData(requestId, timeoutMs);
|
|
253
|
+
// Wait for first data event.
|
|
254
|
+
const firstData = await this.waitForData(requestId, timeoutMs);
|
|
241
255
|
// Collect per-symbol data
|
|
242
256
|
const result = {};
|
|
243
257
|
for (const symbol of symbols) {
|
|
@@ -249,9 +263,8 @@ class SidecarWsClient {
|
|
|
249
263
|
}
|
|
250
264
|
// If no per-symbol data, return the single data event as-is
|
|
251
265
|
if (Object.keys(result).length === 0) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return data;
|
|
266
|
+
if (firstData && typeof firstData === "object") {
|
|
267
|
+
return firstData;
|
|
255
268
|
}
|
|
256
269
|
}
|
|
257
270
|
return result;
|
|
@@ -267,6 +280,8 @@ class SidecarWsClient {
|
|
|
267
280
|
}
|
|
268
281
|
this.ws = null;
|
|
269
282
|
}
|
|
283
|
+
this.dataQueues.clear();
|
|
284
|
+
this.dataStore.clear();
|
|
270
285
|
}
|
|
271
286
|
get connected() {
|
|
272
287
|
return this.ws !== null && !this.closed;
|
|
@@ -275,11 +290,14 @@ class SidecarWsClient {
|
|
|
275
290
|
// Internal
|
|
276
291
|
// ------------------------------------------------------------------
|
|
277
292
|
waitForData(requestId, timeoutMs) {
|
|
278
|
-
// Check if data is already available
|
|
279
|
-
const
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
293
|
+
// Check if queued data is already available.
|
|
294
|
+
const queue = this.dataQueues.get(requestId);
|
|
295
|
+
if (queue && queue.length > 0) {
|
|
296
|
+
const next = queue.shift();
|
|
297
|
+
if (queue.length === 0) {
|
|
298
|
+
this.dataQueues.delete(requestId);
|
|
299
|
+
}
|
|
300
|
+
return Promise.resolve(next);
|
|
283
301
|
}
|
|
284
302
|
return new Promise((resolve, reject) => {
|
|
285
303
|
const sub = this.subscriptions.get(requestId);
|
package/generated/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "2.46.
|
|
3
|
+
"version": "2.46.5",
|
|
4
4
|
"description": "Unified prediction market data API - The ccxt for prediction markets",
|
|
5
5
|
"author": "PMXT Contributors",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"unified"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"pmxt-core": "2.46.
|
|
46
|
+
"pmxt-core": "2.46.5",
|
|
47
47
|
"ws": "^8.18.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
package/pmxt/client.ts
CHANGED
|
@@ -61,6 +61,7 @@ interface SidecarWsClientInternals {
|
|
|
61
61
|
ws: RawWebSocketLike | null;
|
|
62
62
|
activeSubs: Map<string, string>;
|
|
63
63
|
subscriptions: Map<string, { reject: ((error: Error) => void) | null }>;
|
|
64
|
+
dataQueues: Map<string, any[]>;
|
|
64
65
|
dataStore: Map<string, any>;
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -238,6 +239,13 @@ export interface ExchangeOptions {
|
|
|
238
239
|
* prediction market platforms (Polymarket, Kalshi, etc.).
|
|
239
240
|
*/
|
|
240
241
|
export abstract class Exchange {
|
|
242
|
+
private static readonly OBDATA_WATCH_ALL_SOURCES = new Set([
|
|
243
|
+
"polymarket",
|
|
244
|
+
"limitless",
|
|
245
|
+
"kalshi",
|
|
246
|
+
"opinion",
|
|
247
|
+
]);
|
|
248
|
+
|
|
241
249
|
protected exchangeName: string;
|
|
242
250
|
protected apiKey?: string;
|
|
243
251
|
protected privateKey?: string;
|
|
@@ -508,6 +516,13 @@ export abstract class Exchange {
|
|
|
508
516
|
&& /connection failed|no websocket|websocket.*not connected/i.test(error.message);
|
|
509
517
|
}
|
|
510
518
|
|
|
519
|
+
private defaultWatchAllOrderBookVenues(): string[] | undefined {
|
|
520
|
+
if (Exchange.OBDATA_WATCH_ALL_SOURCES.has(this.exchangeName)) {
|
|
521
|
+
return [this.exchangeName];
|
|
522
|
+
}
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
|
|
511
526
|
private getWsInternals(ws: SidecarWsClient): SidecarWsClientInternals {
|
|
512
527
|
return ws as unknown as SidecarWsClientInternals;
|
|
513
528
|
}
|
|
@@ -538,6 +553,7 @@ export abstract class Exchange {
|
|
|
538
553
|
|
|
539
554
|
internals.activeSubs.delete(subKey);
|
|
540
555
|
internals.subscriptions.delete(requestId);
|
|
556
|
+
internals.dataQueues.delete(requestId);
|
|
541
557
|
internals.dataStore.delete(requestId);
|
|
542
558
|
|
|
543
559
|
const firstArg = args[0] ?? "";
|
|
@@ -1718,7 +1734,8 @@ export abstract class Exchange {
|
|
|
1718
1734
|
* Call repeatedly in a loop to stream updates (CCXT Pro pattern).
|
|
1719
1735
|
* Requires hosted mode (`pmxtApiKey` set).
|
|
1720
1736
|
*
|
|
1721
|
-
* @param venues - Optional venue filter
|
|
1737
|
+
* @param venues - Optional venue filter. Defaults to this exchange's venue
|
|
1738
|
+
* for venue clients (e.g. Kalshi -> ["kalshi"]); Router defaults to all venues.
|
|
1722
1739
|
* @returns Next event with source, symbol, and orderbook
|
|
1723
1740
|
*
|
|
1724
1741
|
* @example
|
|
@@ -1737,7 +1754,8 @@ export abstract class Exchange {
|
|
|
1737
1754
|
throw new PmxtError("watchAllOrderBooks() requires hosted mode (set pmxtApiKey)");
|
|
1738
1755
|
}
|
|
1739
1756
|
|
|
1740
|
-
const
|
|
1757
|
+
const effectiveVenues = venues ?? this.defaultWatchAllOrderBookVenues();
|
|
1758
|
+
const args: any[] = effectiveVenues?.length ? [effectiveVenues] : [];
|
|
1741
1759
|
const wsData = await this.watchViaWs("watchAllOrderBooks", args);
|
|
1742
1760
|
if (wsData !== null) {
|
|
1743
1761
|
return {
|
package/pmxt/ws-client.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { PmxtError } from "./errors.js";
|
|
11
11
|
|
|
12
|
+
const MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION = 100_000;
|
|
13
|
+
|
|
12
14
|
interface WsSubscription {
|
|
13
15
|
readonly requestId: string;
|
|
14
16
|
readonly method: string;
|
|
@@ -39,7 +41,9 @@ export class SidecarWsClient {
|
|
|
39
41
|
private authParamName: string;
|
|
40
42
|
private closed = false;
|
|
41
43
|
|
|
42
|
-
/** requestId ->
|
|
44
|
+
/** requestId -> queued data payloads for single-event watch methods */
|
|
45
|
+
private dataQueues: Map<string, any[]> = new Map();
|
|
46
|
+
/** requestId[:symbol] -> latest data payload for batch snapshots */
|
|
43
47
|
private dataStore: Map<string, any> = new Map();
|
|
44
48
|
/** requestId -> subscription metadata */
|
|
45
49
|
private subscriptions: Map<string, WsSubscription> = new Map();
|
|
@@ -189,17 +193,30 @@ export class SidecarWsClient {
|
|
|
189
193
|
const symbol = msg.symbol || "";
|
|
190
194
|
const data = msg.data || {};
|
|
191
195
|
|
|
192
|
-
// Store by (requestId:symbol) for batch methods
|
|
196
|
+
// Store by (requestId:symbol) for batch methods.
|
|
193
197
|
this.dataStore.set(`${requestId}:${symbol}`, data);
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
this.deliverOrQueue(requestId, data);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
196
201
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
private deliverOrQueue(requestId: string, data: any): void {
|
|
203
|
+
const sub = this.subscriptions.get(requestId);
|
|
204
|
+
if (sub?.resolve) {
|
|
205
|
+
sub.resolve(data);
|
|
206
|
+
sub.resolve = null;
|
|
207
|
+
sub.reject = null;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let queue = this.dataQueues.get(requestId);
|
|
212
|
+
if (!queue) {
|
|
213
|
+
queue = [];
|
|
214
|
+
this.dataQueues.set(requestId, queue);
|
|
215
|
+
}
|
|
216
|
+
queue.push(data);
|
|
217
|
+
|
|
218
|
+
if (queue.length > MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION) {
|
|
219
|
+
queue.splice(0, queue.length - MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION);
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
|
|
@@ -297,8 +314,8 @@ export class SidecarWsClient {
|
|
|
297
314
|
}
|
|
298
315
|
this.ws.send(JSON.stringify(message));
|
|
299
316
|
|
|
300
|
-
// Wait for first data event
|
|
301
|
-
await this.waitForData(requestId, timeoutMs);
|
|
317
|
+
// Wait for first data event.
|
|
318
|
+
const firstData = await this.waitForData(requestId, timeoutMs);
|
|
302
319
|
|
|
303
320
|
// Collect per-symbol data
|
|
304
321
|
const result: Record<string, any> = {};
|
|
@@ -312,9 +329,8 @@ export class SidecarWsClient {
|
|
|
312
329
|
|
|
313
330
|
// If no per-symbol data, return the single data event as-is
|
|
314
331
|
if (Object.keys(result).length === 0) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return data;
|
|
332
|
+
if (firstData && typeof firstData === "object") {
|
|
333
|
+
return firstData;
|
|
318
334
|
}
|
|
319
335
|
}
|
|
320
336
|
|
|
@@ -331,6 +347,8 @@ export class SidecarWsClient {
|
|
|
331
347
|
}
|
|
332
348
|
this.ws = null;
|
|
333
349
|
}
|
|
350
|
+
this.dataQueues.clear();
|
|
351
|
+
this.dataStore.clear();
|
|
334
352
|
}
|
|
335
353
|
|
|
336
354
|
get connected(): boolean {
|
|
@@ -342,11 +360,14 @@ export class SidecarWsClient {
|
|
|
342
360
|
// ------------------------------------------------------------------
|
|
343
361
|
|
|
344
362
|
private waitForData(requestId: string, timeoutMs: number): Promise<any> {
|
|
345
|
-
// Check if data is already available
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
// Check if queued data is already available.
|
|
364
|
+
const queue = this.dataQueues.get(requestId);
|
|
365
|
+
if (queue && queue.length > 0) {
|
|
366
|
+
const next = queue.shift();
|
|
367
|
+
if (queue.length === 0) {
|
|
368
|
+
this.dataQueues.delete(requestId);
|
|
369
|
+
}
|
|
370
|
+
return Promise.resolve(next);
|
|
350
371
|
}
|
|
351
372
|
|
|
352
373
|
return new Promise<any>((resolve, reject) => {
|