pmxt-core 2.1.1 → 2.1.2

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.
@@ -4,7 +4,7 @@ export interface MarketFilterParams {
4
4
  limit?: number;
5
5
  offset?: number;
6
6
  sort?: 'volume' | 'liquidity' | 'newest';
7
- status?: 'active' | 'closed' | 'all';
7
+ status?: 'active' | 'inactive' | 'closed' | 'all';
8
8
  searchIn?: 'title' | 'description' | 'both';
9
9
  query?: string;
10
10
  slug?: string;
@@ -17,7 +17,7 @@ export interface EventFetchParams {
17
17
  query?: string;
18
18
  limit?: number;
19
19
  offset?: number;
20
- status?: 'active' | 'closed' | 'all';
20
+ status?: 'active' | 'inactive' | 'closed' | 'all';
21
21
  searchIn?: 'title' | 'description' | 'both';
22
22
  }
23
23
  export interface HistoryFilterParams {
@@ -114,6 +114,8 @@ export declare abstract class PredictionMarketExchange {
114
114
  * @param params.searchIn - Where to search ('title' | 'description' | 'both')
115
115
  * @returns Array of unified markets
116
116
  *
117
+ * @note Some exchanges (like Limitless) may only support status 'active' for search results.
118
+ *
117
119
  * @example-ts Fetch markets
118
120
  * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
119
121
  * console.log(markets[0].title);
@@ -140,6 +142,8 @@ export declare abstract class PredictionMarketExchange {
140
142
  * @param params.searchIn - Where to search ('title' | 'description' | 'both')
141
143
  * @returns Array of unified events
142
144
  *
145
+ * @note Some exchanges (like Limitless) may only support status 'active' for search results.
146
+ *
143
147
  * @example-ts Search events
144
148
  * const events = await exchange.fetchEvents({ query: 'Fed Chair' });
145
149
  * const fedEvent = events[0];
@@ -22,6 +22,8 @@ class PredictionMarketExchange {
22
22
  * @param params.searchIn - Where to search ('title' | 'description' | 'both')
23
23
  * @returns Array of unified markets
24
24
  *
25
+ * @note Some exchanges (like Limitless) may only support status 'active' for search results.
26
+ *
25
27
  * @example-ts Fetch markets
26
28
  * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
27
29
  * console.log(markets[0].title);
@@ -50,6 +52,8 @@ class PredictionMarketExchange {
50
52
  * @param params.searchIn - Where to search ('title' | 'description' | 'both')
51
53
  * @returns Array of unified events
52
54
  *
55
+ * @note Some exchanges (like Limitless) may only support status 'active' for search results.
56
+ *
53
57
  * @example-ts Search events
54
58
  * const events = await exchange.fetchEvents({ query: 'Fed Chair' });
55
59
  * const fedEvent = events[0];
@@ -10,19 +10,57 @@ const errors_1 = require("./errors");
10
10
  async function fetchEvents(params) {
11
11
  try {
12
12
  const status = params?.status || 'active';
13
- let apiStatus = 'open';
14
- if (status === 'closed')
15
- apiStatus = 'closed';
16
- const queryParams = {
17
- limit: 200, // Reasonable batch for search
18
- with_nested_markets: true,
19
- status: apiStatus
13
+ const limit = params?.limit || 10000;
14
+ const query = (params?.query || '').toLowerCase();
15
+ const fetchAllWithStatus = async (apiStatus) => {
16
+ let allEvents = [];
17
+ let cursor = null;
18
+ let page = 0;
19
+ const MAX_PAGES = 1000; // Safety cap against infinite loops
20
+ const BATCH_SIZE = 200; // Max limit per Kalshi API docs
21
+ do {
22
+ const queryParams = {
23
+ limit: BATCH_SIZE,
24
+ with_nested_markets: true,
25
+ status: apiStatus
26
+ };
27
+ if (cursor)
28
+ queryParams.cursor = cursor;
29
+ const response = await axios_1.default.get(utils_1.KALSHI_API_URL, { params: queryParams });
30
+ const events = response.data.events || [];
31
+ if (events.length === 0)
32
+ break;
33
+ allEvents = allEvents.concat(events);
34
+ cursor = response.data.cursor;
35
+ page++;
36
+ // If we have no search query and have fetched enough events, we can stop early
37
+ if (!query && allEvents.length >= limit * 1.5) {
38
+ break;
39
+ }
40
+ } while (cursor && page < MAX_PAGES);
41
+ return allEvents;
20
42
  };
21
- const response = await axios_1.default.get(utils_1.KALSHI_API_URL, { params: queryParams });
22
- const events = response.data.events || [];
23
- const lowerQuery = (params?.query || '').toLowerCase();
43
+ let events = [];
44
+ if (status === 'all') {
45
+ const [openEvents, closedEvents, settledEvents] = await Promise.all([
46
+ fetchAllWithStatus('open'),
47
+ fetchAllWithStatus('closed'),
48
+ fetchAllWithStatus('settled')
49
+ ]);
50
+ events = [...openEvents, ...closedEvents, ...settledEvents];
51
+ }
52
+ else if (status === 'closed' || status === 'inactive') {
53
+ const [closedEvents, settledEvents] = await Promise.all([
54
+ fetchAllWithStatus('closed'),
55
+ fetchAllWithStatus('settled')
56
+ ]);
57
+ events = [...closedEvents, ...settledEvents];
58
+ }
59
+ else {
60
+ events = await fetchAllWithStatus('open');
61
+ }
24
62
  const filtered = events.filter((event) => {
25
- return (event.title || '').toLowerCase().includes(lowerQuery);
63
+ return (event.title || '').toLowerCase().includes(query);
26
64
  });
27
65
  const unifiedEvents = filtered.map((event) => {
28
66
  const markets = [];
@@ -47,7 +85,6 @@ async function fetchEvents(params) {
47
85
  };
48
86
  return unifiedEvent;
49
87
  });
50
- const limit = params?.limit || 10000;
51
88
  return unifiedEvents.slice(0, limit);
52
89
  }
53
90
  catch (error) {
@@ -162,7 +162,7 @@ async function fetchMarketsDefault(params) {
162
162
  // Map 'active' -> 'open', 'closed' -> 'closed'
163
163
  // Kalshi statuses: 'open', 'closed', 'settled'
164
164
  let apiStatus = 'open';
165
- if (status === 'closed')
165
+ if (status === 'closed' || status === 'inactive')
166
166
  apiStatus = 'closed';
167
167
  else if (status === 'all')
168
168
  apiStatus = 'open'; // Fallback for all? Or loop? For now default to open.
@@ -25,14 +25,14 @@ class LimitlessErrorMapper extends error_mapper_1.ErrorMapper {
25
25
  const data = error.response.data;
26
26
  // Limitless uses errorMsg field (CLOB client)
27
27
  if (data.errorMsg) {
28
- return data.errorMsg;
28
+ return typeof data.errorMsg === 'string' ? data.errorMsg : JSON.stringify(data.errorMsg);
29
29
  }
30
30
  // Also check standard error paths
31
31
  if (data.error?.message) {
32
- return data.error.message;
32
+ return String(data.error.message);
33
33
  }
34
34
  if (data.message) {
35
- return data.message;
35
+ return String(data.message);
36
36
  }
37
37
  }
38
38
  return super.extractErrorMessage(error);
@@ -41,7 +41,7 @@ class LimitlessErrorMapper extends error_mapper_1.ErrorMapper {
41
41
  * Override to detect Limitless-specific error patterns
42
42
  */
43
43
  mapBadRequestError(message, data) {
44
- const lowerMessage = message.toLowerCase();
44
+ const lowerMessage = (message || '').toString().toLowerCase();
45
45
  // Limitless-specific authentication errors (400 status)
46
46
  if (lowerMessage.includes('api key') ||
47
47
  lowerMessage.includes('proxy') ||
@@ -8,17 +8,29 @@ const axios_1 = __importDefault(require("axios"));
8
8
  const utils_1 = require("./utils");
9
9
  const errors_1 = require("./errors");
10
10
  async function fetchEvents(params) {
11
- if (params?.status === 'closed') {
12
- return [];
13
- }
14
11
  try {
12
+ // NOTE: The Limitless /markets/search endpoint currently only returns active/funded markets.
13
+ // It does not include expired or resolved markets in search results.
14
+ // Consequently, status 'inactive' will likely return 0 results and 'all' will only show active markets.
15
15
  const response = await axios_1.default.get(`${utils_1.LIMITLESS_API_URL}/markets/search`, {
16
16
  params: {
17
17
  query: params.query,
18
- limit: params?.limit || 10000
18
+ limit: params?.limit || 10000,
19
+ similarityThreshold: 0.5
19
20
  }
20
21
  });
21
- const markets = response.data?.markets || [];
22
+ let markets = response.data?.markets || [];
23
+ // Filter by status based on expired/resolved state
24
+ // Active: not expired and not resolved
25
+ // Inactive: expired OR resolved (has winningOutcomeIndex)
26
+ const status = params?.status || 'active';
27
+ if (status === 'active') {
28
+ markets = markets.filter((m) => !m.expired && m.winningOutcomeIndex === null);
29
+ }
30
+ else if (status === 'inactive' || status === 'closed') {
31
+ markets = markets.filter((m) => m.expired === true || m.winningOutcomeIndex !== null);
32
+ }
33
+ // If status === 'all', don't filter
22
34
  return markets.map((market) => {
23
35
  let marketsList = [];
24
36
  if (market.markets && Array.isArray(market.markets)) {
@@ -1,25 +1,60 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.fetchMarkets = fetchMarkets;
7
- const sdk_1 = require("@limitless-exchange/sdk");
8
40
  const axios_1 = __importDefault(require("axios"));
9
41
  const utils_1 = require("./utils");
10
42
  const errors_1 = require("./errors");
11
43
  async function fetchMarkets(params, apiKey) {
12
44
  // Limitless API currently only supports fetching active markets for lists
13
- if (params?.status === 'closed') {
45
+ // Early return to avoid SDK initialization in tests
46
+ if (params?.status === 'inactive' || params?.status === 'closed') {
14
47
  return [];
15
48
  }
49
+ // Lazy import SDK to avoid initialization when not needed
50
+ const { HttpClient, MarketFetcher } = await Promise.resolve().then(() => __importStar(require('@limitless-exchange/sdk')));
16
51
  try {
17
52
  // Create HTTP client (no auth needed for market data)
18
- const httpClient = new sdk_1.HttpClient({
53
+ const httpClient = new HttpClient({
19
54
  baseURL: utils_1.LIMITLESS_API_URL,
20
55
  apiKey: apiKey, // Optional - not required for public market data
21
56
  });
22
- const marketFetcher = new sdk_1.MarketFetcher(httpClient);
57
+ const marketFetcher = new MarketFetcher(httpClient);
23
58
  // Handle slug-based lookup
24
59
  if (params?.slug) {
25
60
  return await fetchMarketsBySlug(marketFetcher, params.slug);
@@ -44,6 +79,8 @@ async function fetchMarketsBySlug(marketFetcher, slug) {
44
79
  }
45
80
  async function searchMarkets(marketFetcher, query, params) {
46
81
  // SDK doesn't have a search method yet, use axios directly
82
+ // NOTE: The Limitless /markets/search endpoint currently only returns active/funded markets.
83
+ // It does not include expired or resolved markets in search results.
47
84
  const response = await axios_1.default.get(`${utils_1.LIMITLESS_API_URL}/markets/search`, {
48
85
  params: {
49
86
  query: query,
@@ -15,20 +15,45 @@ async function fetchEvents(params) {
15
15
  const status = params.status || 'active';
16
16
  const queryParams = {
17
17
  q: params.query,
18
- limit_per_type: 50, // Fetch 50 per page for better efficiency
19
- events_status: status === 'all' ? undefined : status,
18
+ limit_per_type: 50,
20
19
  sort: 'volume',
21
20
  ascending: false
22
21
  };
23
- // If specific status requested
24
- if (status === 'active') {
25
- queryParams.events_status = 'active';
22
+ const fetchWithStatus = async (eventStatus) => {
23
+ const currentParams = { ...queryParams, events_status: eventStatus };
24
+ return (0, utils_1.paginateSearchParallel)(utils_1.GAMMA_SEARCH_URL, currentParams, limit * 10);
25
+ };
26
+ // Client-side filtering logic
27
+ // The API returns active events when querying for 'closed' status sometimes.
28
+ // We must strictly filter based on the event's `active` and `closed` properties.
29
+ const filterActive = (e) => e.active === true;
30
+ const filterClosed = (e) => e.closed === true;
31
+ let events = [];
32
+ if (status === 'all') {
33
+ const [activeEvents, closedEvents] = await Promise.all([
34
+ fetchWithStatus('active'),
35
+ fetchWithStatus('closed')
36
+ ]);
37
+ // Merge and de-duplicate by ID
38
+ const seenIds = new Set();
39
+ events = [...activeEvents, ...closedEvents].filter(event => {
40
+ const id = event.id || event.slug;
41
+ if (seenIds.has(id))
42
+ return false;
43
+ seenIds.add(id);
44
+ return true;
45
+ });
46
+ }
47
+ else if (status === 'active') {
48
+ const rawEvents = await fetchWithStatus('active');
49
+ events = rawEvents.filter(filterActive);
26
50
  }
27
- else if (status === 'closed') {
28
- queryParams.events_status = 'closed';
51
+ else if (status === 'inactive' || status === 'closed') {
52
+ // Polymarket sometimes returns active events when querying for closed
53
+ // So we fetch 'closed' but strictly filter
54
+ const rawEvents = await fetchWithStatus('closed');
55
+ events = rawEvents.filter(filterClosed);
29
56
  }
30
- // Use parallel pagination to fetch all matching events
31
- const events = await (0, utils_1.paginateSearchParallel)(utils_1.GAMMA_SEARCH_URL, queryParams, limit * 10);
32
57
  // Client-side filtering to ensure title matches (API does fuzzy search)
33
58
  const lowerQuery = params.query.toLowerCase();
34
59
  const searchIn = params.searchIn || 'title';
@@ -65,7 +90,7 @@ async function fetchEvents(params) {
65
90
  };
66
91
  return unifiedEvent;
67
92
  });
68
- return unifiedEvents;
93
+ return unifiedEvents.slice(0, limit);
69
94
  }
70
95
  catch (error) {
71
96
  throw errors_1.polymarketErrorMapper.mapError(error);
@@ -51,7 +51,7 @@ async function searchMarkets(query, params) {
51
51
  const queryParams = {
52
52
  q: query,
53
53
  limit_per_type: 50, // Fetch 50 events per page
54
- events_status: params?.status === 'all' ? undefined : (params?.status || 'active'),
54
+ events_status: params?.status === 'all' ? undefined : (params?.status === 'inactive' || params?.status === 'closed' ? 'closed' : 'active'),
55
55
  sort: 'volume',
56
56
  ascending: false
57
57
  };
@@ -98,7 +98,7 @@ async function fetchMarketsDefault(params) {
98
98
  queryParams.active = 'true';
99
99
  queryParams.closed = 'false';
100
100
  }
101
- else if (status === 'closed') {
101
+ else if (status === 'closed' || status === 'inactive') {
102
102
  queryParams.active = 'false';
103
103
  queryParams.closed = 'true';
104
104
  }
@@ -231,6 +231,14 @@ class ErrorMapper {
231
231
  return error;
232
232
  }
233
233
  // Unknown error format
234
+ if (typeof error === 'object' && error !== null) {
235
+ try {
236
+ return JSON.stringify(error, Object.getOwnPropertyNames(error));
237
+ }
238
+ catch (e) {
239
+ return String(error);
240
+ }
241
+ }
234
242
  return String(error);
235
243
  }
236
244
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
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.1.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.1.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.1.2,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.1.2,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
34
34
  "extract:jsdoc": "node ../scripts/extract-jsdoc.js",
35
35
  "generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",
36
36
  "generate:sdk:all": "npm run generate:sdk:python && npm run generate:sdk:typescript && npm run generate:docs"