pmxt-core 2.44.5 → 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/kalshi/fetcher.d.ts +11 -1
- package/dist/exchanges/kalshi/fetcher.js +49 -17
- package/dist/exchanges/kalshi/normalizer.d.ts +12 -0
- package/dist/exchanges/kalshi/normalizer.js +125 -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
|
@@ -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",
|