pmxt-core 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/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/mock/index.js +13 -2
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/websocket.d.ts +12 -0
- package/dist/exchanges/polymarket/websocket.js +120 -14
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/suibets/api.d.ts +15 -0
- package/dist/exchanges/suibets/api.js +17 -0
- package/dist/exchanges/suibets/config.d.ts +16 -0
- package/dist/exchanges/suibets/config.js +34 -0
- package/dist/exchanges/suibets/errors.d.ts +16 -0
- package/dist/exchanges/suibets/errors.js +71 -0
- package/dist/exchanges/suibets/fetcher.d.ts +64 -0
- package/dist/exchanges/suibets/fetcher.js +128 -0
- package/dist/exchanges/suibets/index.d.ts +54 -0
- package/dist/exchanges/suibets/index.js +114 -0
- package/dist/exchanges/suibets/normalizer.d.ts +8 -0
- package/dist/exchanges/suibets/normalizer.js +102 -0
- package/dist/exchanges/suibets/utils.d.ts +63 -0
- package/dist/exchanges/suibets/utils.js +124 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -1
- package/dist/router/Router.js +12 -3
- package/dist/server/app.js +76 -1
- package/dist/server/exchange-factory.js +6 -0
- package/dist/server/openapi.yaml +7 -0
- package/dist/server/ws-handler.js +196 -23
- package/package.json +6 -6
package/dist/router/Router.js
CHANGED
|
@@ -217,7 +217,10 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
217
217
|
}
|
|
218
218
|
// Lookup mode: find matches for a specific market.
|
|
219
219
|
const response = await this.client.getMarketMatches(params);
|
|
220
|
-
|
|
220
|
+
if (!response || !Array.isArray(response.matches)) {
|
|
221
|
+
throw new Error('fetchMarketMatches returned an unexpected response shape: missing matches array');
|
|
222
|
+
}
|
|
223
|
+
const matches = response.matches;
|
|
221
224
|
return matches.map((m) => ({
|
|
222
225
|
market: m.market,
|
|
223
226
|
relation: m.relation,
|
|
@@ -262,14 +265,20 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
|
|
|
262
265
|
const hasIdentifier = params.eventId || params.slug;
|
|
263
266
|
if (!hasIdentifier) {
|
|
264
267
|
const results = await this.client.browseEventMatches(params);
|
|
265
|
-
|
|
268
|
+
if (!Array.isArray(results)) {
|
|
269
|
+
throw new Error(`browseEventMatches returned unexpected type '${typeof results}'`);
|
|
270
|
+
}
|
|
271
|
+
return results;
|
|
266
272
|
}
|
|
267
273
|
if (await this.resolveLocalMockEventLookup(params)) {
|
|
268
274
|
return [];
|
|
269
275
|
}
|
|
270
276
|
// Lookup mode: find matches for a specific event.
|
|
271
277
|
const response = await this.client.getEventMatches(params);
|
|
272
|
-
|
|
278
|
+
if (!response || !Array.isArray(response.matches)) {
|
|
279
|
+
throw new Error('fetchEventMatches returned an unexpected response shape: missing matches array');
|
|
280
|
+
}
|
|
281
|
+
return response.matches;
|
|
273
282
|
}
|
|
274
283
|
// -----------------------------------------------------------------------
|
|
275
284
|
// Price comparison: identity matches with live prices
|
package/dist/server/app.js
CHANGED
|
@@ -134,6 +134,80 @@ function queryToArgs(query, spec) {
|
|
|
134
134
|
}
|
|
135
135
|
return args;
|
|
136
136
|
}
|
|
137
|
+
function isRecord(value) {
|
|
138
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
function toDateParam(value, field, exchange) {
|
|
141
|
+
if (value instanceof Date) {
|
|
142
|
+
if (Number.isNaN(value.getTime())) {
|
|
143
|
+
throw new errors_1.ValidationError(`${field} must be a valid date-time value.`, field, exchange);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
148
|
+
const parsed = new Date(value);
|
|
149
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
150
|
+
return parsed;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
throw new errors_1.ValidationError(`${field} must be a valid date-time string or timestamp.`, field, exchange);
|
|
154
|
+
}
|
|
155
|
+
function normalizeDateFields(params, fields, exchange) {
|
|
156
|
+
const normalized = { ...params };
|
|
157
|
+
for (const field of fields) {
|
|
158
|
+
const value = normalized[field];
|
|
159
|
+
if (value === undefined || value === null || value === "")
|
|
160
|
+
continue;
|
|
161
|
+
normalized[field] = toDateParam(value, field, exchange);
|
|
162
|
+
}
|
|
163
|
+
return normalized;
|
|
164
|
+
}
|
|
165
|
+
function isMissing(value) {
|
|
166
|
+
return value === undefined || value === null || value === "";
|
|
167
|
+
}
|
|
168
|
+
function assertFiniteNumber(params, field, exchange) {
|
|
169
|
+
const value = params[field];
|
|
170
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
171
|
+
throw new errors_1.ValidationError(`createOrder params.${field} must be a finite number.`, field, exchange);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function validateCreateOrderParams(params, exchange) {
|
|
175
|
+
if (!isRecord(params)) {
|
|
176
|
+
throw new errors_1.ValidationError("createOrder requires an order parameter object.", "params", exchange);
|
|
177
|
+
}
|
|
178
|
+
const required = ["marketId", "outcomeId", "side", "type", "amount"];
|
|
179
|
+
const missing = required.filter((field) => isMissing(params[field]));
|
|
180
|
+
if (missing.length > 0) {
|
|
181
|
+
throw new errors_1.ValidationError(`createOrder requires ${missing.map((field) => `params.${field}`).join(", ")}.`, missing[0], exchange);
|
|
182
|
+
}
|
|
183
|
+
if (params.side !== "buy" && params.side !== "sell") {
|
|
184
|
+
throw new errors_1.ValidationError('createOrder params.side must be "buy" or "sell".', "side", exchange);
|
|
185
|
+
}
|
|
186
|
+
if (params.type !== "market" && params.type !== "limit") {
|
|
187
|
+
throw new errors_1.ValidationError('createOrder params.type must be "market" or "limit".', "type", exchange);
|
|
188
|
+
}
|
|
189
|
+
assertFiniteNumber(params, "amount", exchange);
|
|
190
|
+
if (params.type === "limit" && isMissing(params.price)) {
|
|
191
|
+
throw new errors_1.ValidationError("createOrder params.price is required for limit orders.", "price", exchange);
|
|
192
|
+
}
|
|
193
|
+
if (!isMissing(params.price)) {
|
|
194
|
+
assertFiniteNumber(params, "price", exchange);
|
|
195
|
+
}
|
|
196
|
+
if (!isMissing(params.fee)) {
|
|
197
|
+
assertFiniteNumber(params, "fee", exchange);
|
|
198
|
+
}
|
|
199
|
+
return params;
|
|
200
|
+
}
|
|
201
|
+
function normalizeDispatchArgs(methodName, args, exchange) {
|
|
202
|
+
const normalized = [...args];
|
|
203
|
+
if (methodName === "fetchOHLCV" && isRecord(normalized[1])) {
|
|
204
|
+
normalized[1] = normalizeDateFields(normalized[1], ["start", "end"], exchange);
|
|
205
|
+
}
|
|
206
|
+
if (methodName === "createOrder") {
|
|
207
|
+
normalized[0] = validateCreateOrderParams(normalized[0], exchange);
|
|
208
|
+
}
|
|
209
|
+
return normalized;
|
|
210
|
+
}
|
|
137
211
|
// Singleton instances for local usage (when no credentials provided)
|
|
138
212
|
const defaultExchanges = {
|
|
139
213
|
polymarket: null,
|
|
@@ -275,7 +349,8 @@ function createApp(options = {}) {
|
|
|
275
349
|
});
|
|
276
350
|
return;
|
|
277
351
|
}
|
|
278
|
-
const
|
|
352
|
+
const normalizedArgs = normalizeDispatchArgs(methodName, args, exchangeName);
|
|
353
|
+
const result = await exchange[methodName](...normalizedArgs);
|
|
279
354
|
res.json({ success: true, data: result });
|
|
280
355
|
}
|
|
281
356
|
catch (error) {
|
|
@@ -14,6 +14,7 @@ const smarkets_1 = require("../exchanges/smarkets");
|
|
|
14
14
|
const polymarket_us_1 = require("../exchanges/polymarket_us");
|
|
15
15
|
const hyperliquid_1 = require("../exchanges/hyperliquid");
|
|
16
16
|
const gemini_titan_1 = require("../exchanges/gemini-titan");
|
|
17
|
+
const suibets_1 = require("../exchanges/suibets");
|
|
17
18
|
const mock_1 = require("../exchanges/mock");
|
|
18
19
|
const router_1 = require("../router");
|
|
19
20
|
function createExchange(name, credentials, bearerToken) {
|
|
@@ -103,6 +104,11 @@ function createExchange(name, credentials, bearerToken) {
|
|
|
103
104
|
apiKey: credentials?.apiKey || process.env.GEMINI_API_KEY,
|
|
104
105
|
apiSecret: credentials?.apiSecret || process.env.GEMINI_API_SECRET,
|
|
105
106
|
});
|
|
107
|
+
case "suibets":
|
|
108
|
+
return new suibets_1.SuiBetsExchange({
|
|
109
|
+
walletAddress: credentials?.walletAddress || process.env.SUIBETS_WALLET_ADDRESS,
|
|
110
|
+
baseUrl: credentials?.baseUrl || process.env.SUIBETS_BASE_URL,
|
|
111
|
+
});
|
|
106
112
|
case "mock":
|
|
107
113
|
return new mock_1.MockExchange();
|
|
108
114
|
case "router":
|
package/dist/server/openapi.yaml
CHANGED
|
@@ -4159,6 +4159,13 @@ x-sdk-constructors:
|
|
|
4159
4159
|
tsName: apiSecret
|
|
4160
4160
|
type: string
|
|
4161
4161
|
description: API secret for authentication
|
|
4162
|
+
suibets:
|
|
4163
|
+
className: Suibets
|
|
4164
|
+
params:
|
|
4165
|
+
- name: pmxt_api_key
|
|
4166
|
+
tsName: pmxtApiKey
|
|
4167
|
+
type: string
|
|
4168
|
+
description: PMXT API key for hosted access
|
|
4162
4169
|
mock:
|
|
4163
4170
|
className: Mock
|
|
4164
4171
|
params:
|
|
@@ -4,6 +4,7 @@ exports.createWebSocketHandler = createWebSocketHandler;
|
|
|
4
4
|
const ws_1 = require("ws");
|
|
5
5
|
const errors_1 = require("../errors");
|
|
6
6
|
const exchange_factory_1 = require("./exchange-factory");
|
|
7
|
+
const feed_factory_1 = require("./feed-factory");
|
|
7
8
|
const logger_1 = require("../utils/logger");
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// Streaming methods
|
|
@@ -14,6 +15,10 @@ const WATCH_METHODS = new Set([
|
|
|
14
15
|
"watchOrderBooks",
|
|
15
16
|
"watchTrades",
|
|
16
17
|
]);
|
|
18
|
+
/** Data-feed methods that produce streaming data. */
|
|
19
|
+
const FEED_WATCH_METHODS = new Set([
|
|
20
|
+
"watchTicker",
|
|
21
|
+
]);
|
|
17
22
|
/** Methods for unsubscribing. */
|
|
18
23
|
const UNWATCH_METHODS = {
|
|
19
24
|
unwatchOrderBook: "watchOrderBook",
|
|
@@ -30,7 +35,10 @@ function sendError(ws, id, message, code) {
|
|
|
30
35
|
* Build a unique key for a subscription so we can cancel it.
|
|
31
36
|
*/
|
|
32
37
|
function subscriptionKey(msg) {
|
|
33
|
-
|
|
38
|
+
const target = msg.targetKind === "feed"
|
|
39
|
+
? `feed:${msg.feed}`
|
|
40
|
+
: `exchange:${msg.exchange}`;
|
|
41
|
+
return `${target}:${msg.method}:${JSON.stringify(msg.args)}`;
|
|
34
42
|
}
|
|
35
43
|
/**
|
|
36
44
|
* Start a streaming loop for a single-ticker watch method
|
|
@@ -101,6 +109,78 @@ async function streamBatch(exchange, method, args, id, ws, signal) {
|
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Start a streaming loop for feed watch methods.
|
|
114
|
+
*
|
|
115
|
+
* Data feeds expose callback-driven watch methods rather than the exchange
|
|
116
|
+
* layer's await-next-update methods, so lifecycle is tied to the returned
|
|
117
|
+
* unsubscribe callback and the client's AbortSignal.
|
|
118
|
+
*/
|
|
119
|
+
async function streamFeedSingle(feed, feedName, method, args, id, ws, signal) {
|
|
120
|
+
const symbol = typeof args[0] === "string" ? args[0] : String(args[0] ?? "");
|
|
121
|
+
let unsubscribe;
|
|
122
|
+
let settled = false;
|
|
123
|
+
let resolveDone = () => { };
|
|
124
|
+
const done = new Promise((resolve) => {
|
|
125
|
+
resolveDone = resolve;
|
|
126
|
+
});
|
|
127
|
+
const cleanup = () => {
|
|
128
|
+
if (settled)
|
|
129
|
+
return;
|
|
130
|
+
settled = true;
|
|
131
|
+
signal.removeEventListener("abort", cleanup);
|
|
132
|
+
ws.removeListener("close", cleanup);
|
|
133
|
+
if (unsubscribe) {
|
|
134
|
+
try {
|
|
135
|
+
unsubscribe();
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
logger_1.logger.warn('ws-handler: feed unsubscribe failed', {
|
|
139
|
+
feed: feedName,
|
|
140
|
+
method,
|
|
141
|
+
id,
|
|
142
|
+
error: err instanceof Error ? err.message : String(err),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
resolveDone();
|
|
147
|
+
};
|
|
148
|
+
signal.addEventListener("abort", cleanup, { once: true });
|
|
149
|
+
ws.once("close", cleanup);
|
|
150
|
+
try {
|
|
151
|
+
if (typeof feed.connect === "function") {
|
|
152
|
+
await feed.connect();
|
|
153
|
+
}
|
|
154
|
+
if (signal.aborted || ws.readyState !== ws_1.WebSocket.OPEN) {
|
|
155
|
+
cleanup();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (method !== "watchTicker" || typeof feed.watchTicker !== "function") {
|
|
159
|
+
sendError(ws, id, `Method '${method}' not found on ${feedName}`);
|
|
160
|
+
cleanup();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
unsubscribe = feed.watchTicker(symbol, (data) => {
|
|
164
|
+
if (!signal.aborted && ws.readyState === ws_1.WebSocket.OPEN) {
|
|
165
|
+
send(ws, { id, event: "data", method, symbol, source: feedName, data });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
send(ws, { event: "subscribed", id });
|
|
169
|
+
await done;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (!signal.aborted) {
|
|
173
|
+
const message = err instanceof errors_1.BaseError
|
|
174
|
+
? err.message
|
|
175
|
+
: err instanceof Error
|
|
176
|
+
? err.message
|
|
177
|
+
: "Unknown streaming error";
|
|
178
|
+
const code = err instanceof errors_1.BaseError ? err.code : undefined;
|
|
179
|
+
sendError(ws, id, message, code);
|
|
180
|
+
}
|
|
181
|
+
cleanup();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
104
184
|
// ---------------------------------------------------------------------------
|
|
105
185
|
// Public API
|
|
106
186
|
// ---------------------------------------------------------------------------
|
|
@@ -158,32 +238,75 @@ function createWebSocketHandler(options = {}) {
|
|
|
158
238
|
const id = parsed.id;
|
|
159
239
|
const action = parsed.action;
|
|
160
240
|
const exchange = parsed.exchange;
|
|
241
|
+
const feed = parsed.feed;
|
|
161
242
|
const method = parsed.method;
|
|
162
|
-
if (!id || !action
|
|
163
|
-
sendError(ws, id, "Missing required fields: id, action
|
|
243
|
+
if (!id || !action) {
|
|
244
|
+
sendError(ws, id, "Missing required fields: id, action");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (action === "unsubscribe" && !method && !exchange && !feed) {
|
|
248
|
+
handleUnsubscribeById(ws, state, id);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!method) {
|
|
252
|
+
sendError(ws, id, "Missing required field: method");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if ((exchange && feed) || (!exchange && !feed)) {
|
|
256
|
+
sendError(ws, id, "Missing required target: provide exactly one of exchange or feed");
|
|
164
257
|
return;
|
|
165
258
|
}
|
|
166
|
-
const exchangeName = exchange.toLowerCase();
|
|
167
259
|
if (action === "subscribe") {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
260
|
+
if (feed) {
|
|
261
|
+
const feedName = feed.toLowerCase();
|
|
262
|
+
const msg = {
|
|
263
|
+
id,
|
|
264
|
+
action: "subscribe",
|
|
265
|
+
targetKind: "feed",
|
|
266
|
+
feed: feedName,
|
|
267
|
+
method,
|
|
268
|
+
args: parsed.args || [],
|
|
269
|
+
};
|
|
270
|
+
handleFeedSubscribe(ws, state, msg, feedName);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const exchangeName = exchange.toLowerCase();
|
|
274
|
+
const msg = {
|
|
275
|
+
id,
|
|
276
|
+
action: "subscribe",
|
|
277
|
+
targetKind: "exchange",
|
|
278
|
+
exchange: exchangeName,
|
|
279
|
+
method,
|
|
280
|
+
args: parsed.args || [],
|
|
281
|
+
credentials: parsed.credentials,
|
|
282
|
+
};
|
|
283
|
+
handleSubscribe(ws, state, msg, exchangeName);
|
|
284
|
+
}
|
|
177
285
|
}
|
|
178
286
|
else if (action === "unsubscribe") {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
287
|
+
if (feed) {
|
|
288
|
+
const msg = {
|
|
289
|
+
id,
|
|
290
|
+
action: "unsubscribe",
|
|
291
|
+
targetKind: "feed",
|
|
292
|
+
feed: feed.toLowerCase(),
|
|
293
|
+
method,
|
|
294
|
+
args: parsed.args || [],
|
|
295
|
+
};
|
|
296
|
+
handleUnsubscribe(ws, state, msg);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const exchangeName = exchange.toLowerCase();
|
|
300
|
+
const msg = {
|
|
301
|
+
id,
|
|
302
|
+
action: "unsubscribe",
|
|
303
|
+
targetKind: "exchange",
|
|
304
|
+
exchange: exchangeName,
|
|
305
|
+
method,
|
|
306
|
+
args: parsed.args || [],
|
|
307
|
+
};
|
|
308
|
+
handleUnsubscribe(ws, state, msg);
|
|
309
|
+
}
|
|
187
310
|
}
|
|
188
311
|
else {
|
|
189
312
|
sendError(ws, id, `Unknown action: ${action}`);
|
|
@@ -257,7 +380,7 @@ function handleSubscribe(ws, state, msg, exchangeName) {
|
|
|
257
380
|
return;
|
|
258
381
|
}
|
|
259
382
|
const abortController = new AbortController();
|
|
260
|
-
state.subscriptions.set(key, { abortController });
|
|
383
|
+
state.subscriptions.set(key, { id, abortController });
|
|
261
384
|
const streamFn = method === "watchOrderBooks" ? streamBatch : streamSingle;
|
|
262
385
|
// Fire and forget -- the loop runs until aborted or WS closes
|
|
263
386
|
streamFn(exchange, method, args || [], id, ws, abortController.signal).catch((err) => {
|
|
@@ -270,10 +393,45 @@ function handleSubscribe(ws, state, msg, exchangeName) {
|
|
|
270
393
|
error: err instanceof Error ? err.message : String(err),
|
|
271
394
|
});
|
|
272
395
|
sendError(ws, id, err instanceof Error ? err.message : 'Streaming error');
|
|
396
|
+
}).finally(() => {
|
|
273
397
|
state.subscriptions.delete(key);
|
|
274
398
|
});
|
|
275
399
|
}
|
|
276
|
-
function
|
|
400
|
+
function handleFeedSubscribe(ws, state, msg, feedName) {
|
|
401
|
+
const { id, method, args } = msg;
|
|
402
|
+
if (!FEED_WATCH_METHODS.has(method)) {
|
|
403
|
+
sendError(ws, id, `Method '${method}' is not a feed streaming method. Use the HTTP API for non-streaming calls.`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const key = subscriptionKey(msg);
|
|
407
|
+
if (state.subscriptions.has(key)) {
|
|
408
|
+
sendError(ws, id, `Already subscribed to ${key}`);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
let feed;
|
|
412
|
+
try {
|
|
413
|
+
feed = (0, feed_factory_1.getFeed)(feedName);
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
const message = err instanceof Error ? err.message : "Failed to create feed";
|
|
417
|
+
sendError(ws, id, message);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const abortController = new AbortController();
|
|
421
|
+
state.subscriptions.set(key, { id, abortController });
|
|
422
|
+
streamFeedSingle(feed, feedName, method, args || [], id, ws, abortController.signal).catch((err) => {
|
|
423
|
+
logger_1.logger.warn('ws-handler: feed stream ended with unexpected error', {
|
|
424
|
+
feed: feedName,
|
|
425
|
+
method,
|
|
426
|
+
id,
|
|
427
|
+
error: err instanceof Error ? err.message : String(err),
|
|
428
|
+
});
|
|
429
|
+
sendError(ws, id, err instanceof Error ? err.message : 'Streaming error');
|
|
430
|
+
}).finally(() => {
|
|
431
|
+
state.subscriptions.delete(key);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function handleUnsubscribe(ws, state, msg) {
|
|
277
435
|
const { id } = msg;
|
|
278
436
|
// Build the key of the subscription to cancel.
|
|
279
437
|
// For unsubscribe actions, the corresponding subscribe key uses the watch method.
|
|
@@ -289,3 +447,18 @@ function handleUnsubscribe(ws, state, msg, exchangeName) {
|
|
|
289
447
|
state.subscriptions.delete(key);
|
|
290
448
|
send(ws, { event: "subscribed", id }); // Acknowledge unsubscribe
|
|
291
449
|
}
|
|
450
|
+
function handleUnsubscribeById(ws, state, id) {
|
|
451
|
+
let cancelled = false;
|
|
452
|
+
for (const [key, sub] of state.subscriptions) {
|
|
453
|
+
if (sub.id === id) {
|
|
454
|
+
sub.abortController.abort();
|
|
455
|
+
state.subscriptions.delete(key);
|
|
456
|
+
cancelled = true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (!cancelled) {
|
|
460
|
+
sendError(ws, id, `No active subscription for id ${id}`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
send(ws, { event: "subscribed", id });
|
|
464
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.45.0",
|
|
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",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/pmxt-dev/pmxt.git"
|
|
9
|
+
"url": "git+https://github.com/pmxt-dev/pmxt.git"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
|
-
"pmxt-server": "
|
|
13
|
-
"pmxt-ensure-server": "
|
|
12
|
+
"pmxt-server": "dist/server/index.js",
|
|
13
|
+
"pmxt-ensure-server": "bin/pmxt-ensure-server"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
@@ -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.
|
|
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.
|
|
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.45.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.45.0,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",
|