pmxtjs 2.44.6 → 2.45.0
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/index.d.ts +2 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/pmxt/client.d.ts +20 -4
- package/dist/esm/pmxt/client.js +114 -115
- package/dist/esm/pmxt/router.js +12 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -1
- package/dist/pmxt/client.d.ts +20 -4
- package/dist/pmxt/client.js +116 -116
- package/dist/pmxt/router.js +12 -6
- package/generated/package.json +1 -1
- package/index.ts +2 -1
- package/package.json +4 -4
- package/pmxt/client.ts +153 -119
- package/pmxt/router.ts +10 -4
package/pmxt/client.ts
CHANGED
|
@@ -52,6 +52,18 @@ import { PmxtError, fromServerError } from "./errors.js";
|
|
|
52
52
|
import { LOCAL_URL, resolvePmxtBaseUrl } from "./constants.js";
|
|
53
53
|
import { SidecarWsClient } from "./ws-client.js";
|
|
54
54
|
|
|
55
|
+
interface RawWebSocketLike {
|
|
56
|
+
send(data: string): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SidecarWsClientInternals {
|
|
60
|
+
ensureConnected(): Promise<void>;
|
|
61
|
+
ws: RawWebSocketLike | null;
|
|
62
|
+
activeSubs: Map<string, string>;
|
|
63
|
+
subscriptions: Map<string, { reject: ((error: Error) => void) | null }>;
|
|
64
|
+
dataStore: Map<string, any>;
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
/**
|
|
56
68
|
* Resolve a MarketOutcome shorthand to a plain outcome ID string.
|
|
57
69
|
* Accepts either a raw string ID or a MarketOutcome object.
|
|
@@ -418,7 +430,7 @@ export abstract class Exchange {
|
|
|
418
430
|
* Return the shared WebSocket client, creating it on first use.
|
|
419
431
|
*
|
|
420
432
|
* Returns `null` if the sidecar /ws endpoint was previously found
|
|
421
|
-
* to be unavailable
|
|
433
|
+
* to be unavailable.
|
|
422
434
|
*/
|
|
423
435
|
private async getOrCreateWs(): Promise<SidecarWsClient | null> {
|
|
424
436
|
if (this._wsUnsupported) return null;
|
|
@@ -480,14 +492,79 @@ export abstract class Exchange {
|
|
|
480
492
|
this.getCredentials() as Record<string, any> | undefined,
|
|
481
493
|
);
|
|
482
494
|
} catch (error) {
|
|
483
|
-
|
|
484
|
-
if (error instanceof PmxtError && /connection failed|no websocket/i.test(error.message)) {
|
|
495
|
+
if (this.isWsTransportUnavailableError(error)) {
|
|
485
496
|
return null;
|
|
486
497
|
}
|
|
487
498
|
throw error;
|
|
488
499
|
}
|
|
489
500
|
}
|
|
490
501
|
|
|
502
|
+
private wsTransportUnavailableError(method: string): PmxtError {
|
|
503
|
+
return new PmxtError(`${method}() requires WebSocket transport — connection failed`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private isWsTransportUnavailableError(error: unknown): boolean {
|
|
507
|
+
return error instanceof PmxtError
|
|
508
|
+
&& /connection failed|no websocket|websocket.*not connected/i.test(error.message);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private getWsInternals(ws: SidecarWsClient): SidecarWsClientInternals {
|
|
512
|
+
return ws as unknown as SidecarWsClientInternals;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private wsSubscriptionKey(method: string, args: any[]): string {
|
|
516
|
+
const firstArg = args[0] ?? "";
|
|
517
|
+
return Array.isArray(firstArg)
|
|
518
|
+
? `${method}:${[...firstArg].sort().join(",")}`
|
|
519
|
+
: `${method}:${firstArg}`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private getWsSubscriptionId(ws: SidecarWsClient, method: string, args: any[]): string | undefined {
|
|
523
|
+
const internals = this.getWsInternals(ws);
|
|
524
|
+
const subKey = this.wsSubscriptionKey(method, args);
|
|
525
|
+
return internals.activeSubs.get(subKey);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private clearWsSubscription(ws: SidecarWsClient, method: string, args: any[]): void {
|
|
529
|
+
const internals = this.getWsInternals(ws);
|
|
530
|
+
const subKey = this.wsSubscriptionKey(method, args);
|
|
531
|
+
const requestId = internals.activeSubs.get(subKey);
|
|
532
|
+
if (!requestId) return;
|
|
533
|
+
|
|
534
|
+
const sub = internals.subscriptions.get(requestId);
|
|
535
|
+
if (sub?.reject) {
|
|
536
|
+
sub.reject(new PmxtError(`${method} subscription cancelled`));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
internals.activeSubs.delete(subKey);
|
|
540
|
+
internals.subscriptions.delete(requestId);
|
|
541
|
+
internals.dataStore.delete(requestId);
|
|
542
|
+
|
|
543
|
+
const firstArg = args[0] ?? "";
|
|
544
|
+
const symbols = Array.isArray(firstArg)
|
|
545
|
+
? firstArg.map(String)
|
|
546
|
+
: firstArg
|
|
547
|
+
? [String(firstArg)]
|
|
548
|
+
: [];
|
|
549
|
+
for (const symbol of symbols) {
|
|
550
|
+
internals.dataStore.delete(`${requestId}:${symbol}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private async sendWsMessage(
|
|
555
|
+
ws: SidecarWsClient,
|
|
556
|
+
message: Record<string, any>,
|
|
557
|
+
): Promise<void> {
|
|
558
|
+
const internals = this.getWsInternals(ws);
|
|
559
|
+
await internals.ensureConnected();
|
|
560
|
+
|
|
561
|
+
const socket = internals.ws;
|
|
562
|
+
if (!socket) {
|
|
563
|
+
throw new PmxtError('[ws-client] Cannot send: WebSocket not connected');
|
|
564
|
+
}
|
|
565
|
+
socket.send(JSON.stringify(message));
|
|
566
|
+
}
|
|
567
|
+
|
|
491
568
|
// Low-Level API Access
|
|
492
569
|
|
|
493
570
|
/**
|
|
@@ -1122,24 +1199,32 @@ export abstract class Exchange {
|
|
|
1122
1199
|
|
|
1123
1200
|
async unwatchOrderBook(outcomeId: string | MarketOutcome): Promise<void> {
|
|
1124
1201
|
await this.initPromise;
|
|
1202
|
+
const resolvedOutcomeId = resolveOutcomeId(outcomeId);
|
|
1203
|
+
const args: any[] = [resolvedOutcomeId];
|
|
1125
1204
|
try {
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
method: 'POST',
|
|
1130
|
-
headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
|
|
1131
|
-
body: JSON.stringify({ args, credentials: this.getCredentials() }),
|
|
1132
|
-
});
|
|
1133
|
-
if (!response.ok) {
|
|
1134
|
-
const body = await response.json().catch(() => ({}));
|
|
1135
|
-
if (body.error && typeof body.error === "object") {
|
|
1136
|
-
throw fromServerError(body.error);
|
|
1137
|
-
}
|
|
1138
|
-
throw new PmxtError(body.error?.message || response.statusText);
|
|
1205
|
+
const ws = await this.getOrCreateWs();
|
|
1206
|
+
if (!ws) {
|
|
1207
|
+
throw this.wsTransportUnavailableError("unwatchOrderBook");
|
|
1139
1208
|
}
|
|
1140
|
-
|
|
1141
|
-
this.
|
|
1209
|
+
|
|
1210
|
+
const requestId = this.getWsSubscriptionId(ws, "watchOrderBook", args)
|
|
1211
|
+
?? `req-${Math.random().toString(36).slice(2, 14)}`;
|
|
1212
|
+
|
|
1213
|
+
await this.sendWsMessage(
|
|
1214
|
+
ws,
|
|
1215
|
+
{
|
|
1216
|
+
id: requestId,
|
|
1217
|
+
action: "unsubscribe",
|
|
1218
|
+
exchange: this.exchangeName,
|
|
1219
|
+
method: "unwatchOrderBook",
|
|
1220
|
+
args,
|
|
1221
|
+
},
|
|
1222
|
+
);
|
|
1223
|
+
this.clearWsSubscription(ws, "watchOrderBook", args);
|
|
1142
1224
|
} catch (error) {
|
|
1225
|
+
if (this.isWsTransportUnavailableError(error)) {
|
|
1226
|
+
throw this.wsTransportUnavailableError("unwatchOrderBook");
|
|
1227
|
+
}
|
|
1143
1228
|
if (error instanceof PmxtError) throw error;
|
|
1144
1229
|
throw new PmxtError(`Failed to unwatchOrderBook: ${error}`);
|
|
1145
1230
|
}
|
|
@@ -1545,33 +1630,12 @@ export abstract class Exchange {
|
|
|
1545
1630
|
args.push(params);
|
|
1546
1631
|
}
|
|
1547
1632
|
|
|
1548
|
-
// Try WebSocket transport first
|
|
1549
1633
|
const wsData = await this.watchViaWs("watchOrderBook", args);
|
|
1550
1634
|
if (wsData !== null) {
|
|
1551
1635
|
return convertOrderBook(wsData);
|
|
1552
1636
|
}
|
|
1553
1637
|
|
|
1554
|
-
|
|
1555
|
-
try {
|
|
1556
|
-
const response = await this.fetchWithRetry(`${this.resolveBaseUrl()}/api/${this.exchangeName}/watchOrderBook`, {
|
|
1557
|
-
method: 'POST',
|
|
1558
|
-
headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
|
|
1559
|
-
body: JSON.stringify({ args, credentials: this.getCredentials() }),
|
|
1560
|
-
});
|
|
1561
|
-
if (!response.ok) {
|
|
1562
|
-
const body = await response.json().catch(() => ({}));
|
|
1563
|
-
if (body.error && typeof body.error === "object") {
|
|
1564
|
-
throw fromServerError(body.error);
|
|
1565
|
-
}
|
|
1566
|
-
throw new PmxtError(body.error?.message || response.statusText);
|
|
1567
|
-
}
|
|
1568
|
-
const json = await response.json();
|
|
1569
|
-
const data = this.handleResponse(json);
|
|
1570
|
-
return convertOrderBook(data);
|
|
1571
|
-
} catch (error) {
|
|
1572
|
-
if (error instanceof PmxtError) throw error;
|
|
1573
|
-
throw new PmxtError(`Failed to watch order book: ${error}`);
|
|
1574
|
-
}
|
|
1638
|
+
throw this.wsTransportUnavailableError("watchOrderBook");
|
|
1575
1639
|
}
|
|
1576
1640
|
|
|
1577
1641
|
/**
|
|
@@ -1581,9 +1645,6 @@ export abstract class Exchange {
|
|
|
1581
1645
|
* order book snapshot. Call repeatedly in a loop to stream updates
|
|
1582
1646
|
* (CCXT Pro pattern).
|
|
1583
1647
|
*
|
|
1584
|
-
* Prefers the sidecar WebSocket transport when available, falling
|
|
1585
|
-
* back to HTTP POST for older sidecars.
|
|
1586
|
-
*
|
|
1587
1648
|
* @param outcomeIds - Array of outcome IDs (or MarketOutcome objects)
|
|
1588
1649
|
* @param limit - Optional depth limit for each order book
|
|
1589
1650
|
* @param params - Optional exchange-specific parameters
|
|
@@ -1618,61 +1679,33 @@ export abstract class Exchange {
|
|
|
1618
1679
|
args.push(params);
|
|
1619
1680
|
}
|
|
1620
1681
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
const rawResult = await ws.subscribeBatch(
|
|
1626
|
-
this.exchangeName,
|
|
1627
|
-
"watchOrderBooks",
|
|
1628
|
-
args,
|
|
1629
|
-
this.getCredentials() as Record<string, any> | undefined,
|
|
1630
|
-
);
|
|
1631
|
-
if (rawResult && typeof rawResult === "object") {
|
|
1632
|
-
const result: Record<string, OrderBook> = {};
|
|
1633
|
-
for (const [k, v] of Object.entries(rawResult)) {
|
|
1634
|
-
if (v && typeof v === "object") {
|
|
1635
|
-
result[k] = convertOrderBook(v);
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
return result;
|
|
1639
|
-
}
|
|
1640
|
-
} catch (error) {
|
|
1641
|
-
// Only fall through to HTTP for transport-level WS failures
|
|
1642
|
-
if (!(error instanceof PmxtError) || !/connection failed|no websocket/i.test(error.message)) {
|
|
1643
|
-
throw error;
|
|
1644
|
-
}
|
|
1682
|
+
try {
|
|
1683
|
+
const ws = await this.getOrCreateWs();
|
|
1684
|
+
if (!ws) {
|
|
1685
|
+
throw this.wsTransportUnavailableError("watchOrderBooks");
|
|
1645
1686
|
}
|
|
1646
|
-
}
|
|
1647
1687
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
method: 'POST',
|
|
1654
|
-
headers: { 'Content-Type': 'application/json', ...this.getAuthHeaders() },
|
|
1655
|
-
body: JSON.stringify({ args, credentials: this.getCredentials() }),
|
|
1656
|
-
},
|
|
1688
|
+
const rawResult = await ws.subscribeBatch(
|
|
1689
|
+
this.exchangeName,
|
|
1690
|
+
"watchOrderBooks",
|
|
1691
|
+
args,
|
|
1692
|
+
this.getCredentials() as Record<string, any> | undefined,
|
|
1657
1693
|
);
|
|
1658
|
-
if (
|
|
1659
|
-
const body = await response.json().catch(() => ({}));
|
|
1660
|
-
if (body.error && typeof body.error === "object") {
|
|
1661
|
-
throw fromServerError(body.error);
|
|
1662
|
-
}
|
|
1663
|
-
throw new PmxtError(body.error?.message || response.statusText);
|
|
1664
|
-
}
|
|
1665
|
-
const json = await response.json();
|
|
1666
|
-
const data = this.handleResponse(json);
|
|
1667
|
-
if (data && typeof data === "object") {
|
|
1694
|
+
if (rawResult && typeof rawResult === "object") {
|
|
1668
1695
|
const result: Record<string, OrderBook> = {};
|
|
1669
|
-
for (const [k, v] of Object.entries(
|
|
1670
|
-
|
|
1696
|
+
for (const [k, v] of Object.entries(rawResult)) {
|
|
1697
|
+
if (v && typeof v === "object") {
|
|
1698
|
+
result[k] = convertOrderBook(v);
|
|
1699
|
+
}
|
|
1671
1700
|
}
|
|
1672
1701
|
return result;
|
|
1673
1702
|
}
|
|
1703
|
+
|
|
1674
1704
|
throw new PmxtError("watchOrderBooks: unexpected response shape from server");
|
|
1675
1705
|
} catch (error) {
|
|
1706
|
+
if (this.isWsTransportUnavailableError(error)) {
|
|
1707
|
+
throw this.wsTransportUnavailableError("watchOrderBooks");
|
|
1708
|
+
}
|
|
1676
1709
|
if (error instanceof PmxtError) throw error;
|
|
1677
1710
|
throw new PmxtError(`Failed to watch order books: ${error}`);
|
|
1678
1711
|
}
|
|
@@ -1714,7 +1747,7 @@ export abstract class Exchange {
|
|
|
1714
1747
|
};
|
|
1715
1748
|
}
|
|
1716
1749
|
|
|
1717
|
-
throw
|
|
1750
|
+
throw this.wsTransportUnavailableError("watchAllOrderBooks");
|
|
1718
1751
|
}
|
|
1719
1752
|
|
|
1720
1753
|
/** @deprecated Use {@link watchAllOrderBooks} instead. */
|
|
@@ -1753,37 +1786,23 @@ export abstract class Exchange {
|
|
|
1753
1786
|
): Promise<Trade[]> {
|
|
1754
1787
|
await this.initPromise;
|
|
1755
1788
|
const resolvedOutcomeId = resolveOutcomeId(outcomeId);
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
}
|
|
1789
|
+
const args: any[] = [resolvedOutcomeId];
|
|
1790
|
+
if (address !== undefined) {
|
|
1791
|
+
args.push(address);
|
|
1792
|
+
}
|
|
1793
|
+
if (since !== undefined) {
|
|
1794
|
+
args.push(since);
|
|
1795
|
+
}
|
|
1796
|
+
if (limit !== undefined) {
|
|
1797
|
+
args.push(limit);
|
|
1798
|
+
}
|
|
1767
1799
|
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
body: JSON.stringify({ args, credentials: this.getCredentials() }),
|
|
1772
|
-
});
|
|
1773
|
-
if (!response.ok) {
|
|
1774
|
-
const body = await response.json().catch(() => ({}));
|
|
1775
|
-
if (body.error && typeof body.error === "object") {
|
|
1776
|
-
throw fromServerError(body.error);
|
|
1777
|
-
}
|
|
1778
|
-
throw new PmxtError(body.error?.message || response.statusText);
|
|
1779
|
-
}
|
|
1780
|
-
const json = await response.json();
|
|
1781
|
-
const data = this.handleResponse(json);
|
|
1782
|
-
return data.map(convertTrade);
|
|
1783
|
-
} catch (error) {
|
|
1784
|
-
if (error instanceof PmxtError) throw error;
|
|
1785
|
-
throw new PmxtError(`Failed to watch trades: ${error}`);
|
|
1800
|
+
const wsData = await this.watchViaWs("watchTrades", args);
|
|
1801
|
+
if (wsData !== null) {
|
|
1802
|
+
return wsData.map(convertTrade);
|
|
1786
1803
|
}
|
|
1804
|
+
|
|
1805
|
+
throw this.wsTransportUnavailableError("watchTrades");
|
|
1787
1806
|
}
|
|
1788
1807
|
|
|
1789
1808
|
/**
|
|
@@ -2760,6 +2779,21 @@ export class Hyperliquid extends Exchange {
|
|
|
2760
2779
|
}
|
|
2761
2780
|
}
|
|
2762
2781
|
|
|
2782
|
+
/**
|
|
2783
|
+
* SuiBets exchange client.
|
|
2784
|
+
*
|
|
2785
|
+
* @example
|
|
2786
|
+
* ```typescript
|
|
2787
|
+
* const suibets = new SuiBets();
|
|
2788
|
+
* const markets = await suibets.fetchMarkets();
|
|
2789
|
+
* ```
|
|
2790
|
+
*/
|
|
2791
|
+
export class SuiBets extends Exchange {
|
|
2792
|
+
constructor(options: ExchangeOptions = {}) {
|
|
2793
|
+
super("suibets", options);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2763
2797
|
/**
|
|
2764
2798
|
* Mock exchange client.
|
|
2765
2799
|
*
|
package/pmxt/router.ts
CHANGED
|
@@ -242,9 +242,9 @@ export class Router extends Exchange {
|
|
|
242
242
|
const params = 'title' in marketOrParams ? { market: marketOrParams as UnifiedMarket } : marketOrParams;
|
|
243
243
|
await this.initPromise;
|
|
244
244
|
const query: Record<string, unknown> = {};
|
|
245
|
-
const marketId = params.marketId ?? params.market?.marketId;
|
|
245
|
+
const marketId = params.marketId ?? (!params.market?.slug ? params.market?.marketId : undefined);
|
|
246
246
|
if (marketId) query.marketId = marketId;
|
|
247
|
-
if (params.slug) query.slug = params.slug;
|
|
247
|
+
if (params.slug ?? params.market?.slug) query.slug = params.slug ?? params.market?.slug;
|
|
248
248
|
if (params.url) query.url = params.url;
|
|
249
249
|
if (params.relation) query.relation = params.relation;
|
|
250
250
|
if (params.minConfidence !== undefined) query.minConfidence = params.minConfidence;
|
|
@@ -255,6 +255,9 @@ export class Router extends Exchange {
|
|
|
255
255
|
const json = await this.sidecarReadRequest('fetchMarketMatches', query, [query]);
|
|
256
256
|
const data = this.handleResponse(json);
|
|
257
257
|
if (!data) return [];
|
|
258
|
+
if (!Array.isArray(data)) {
|
|
259
|
+
throw new Error('fetchMarketMatches returned an unexpected response shape: expected an array');
|
|
260
|
+
}
|
|
258
261
|
return (data as any[]).map(parseMatchResult);
|
|
259
262
|
} catch (error) {
|
|
260
263
|
if (error instanceof Error) throw error;
|
|
@@ -317,9 +320,9 @@ export class Router extends Exchange {
|
|
|
317
320
|
const params = 'title' in eventOrParams && 'markets' in eventOrParams ? { event: eventOrParams as UnifiedEvent } : eventOrParams;
|
|
318
321
|
await this.initPromise;
|
|
319
322
|
const query: Record<string, unknown> = {};
|
|
320
|
-
const eventId = params.eventId ?? params.event?.id;
|
|
323
|
+
const eventId = params.eventId ?? (!params.event?.slug ? params.event?.id : undefined);
|
|
321
324
|
if (eventId) query.eventId = eventId;
|
|
322
|
-
if (params.slug) query.slug = params.slug;
|
|
325
|
+
if (params.slug ?? params.event?.slug) query.slug = params.slug ?? params.event?.slug;
|
|
323
326
|
if (params.relation) query.relation = params.relation;
|
|
324
327
|
if (params.minConfidence !== undefined) query.minConfidence = params.minConfidence;
|
|
325
328
|
if (params.limit !== undefined) query.limit = params.limit;
|
|
@@ -329,6 +332,9 @@ export class Router extends Exchange {
|
|
|
329
332
|
const json = await this.sidecarReadRequest('fetchEventMatches', query, [query]);
|
|
330
333
|
const data = this.handleResponse(json);
|
|
331
334
|
if (!data) return [];
|
|
335
|
+
if (!Array.isArray(data)) {
|
|
336
|
+
throw new Error('fetchEventMatches returned an unexpected response shape: expected an array');
|
|
337
|
+
}
|
|
332
338
|
return (data as any[]).map((entry) => {
|
|
333
339
|
const event = convertEvent(entry.event || {});
|
|
334
340
|
return {
|