pmxt-core 2.1.0 → 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,15 +114,17 @@ 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
- * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 20 });
120
+ * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
119
121
  * console.log(markets[0].title);
120
122
  *
121
123
  * @example-ts Get market by slug
122
124
  * const markets = await exchange.fetchMarkets({ slug: 'will-trump-win' });
123
125
  *
124
126
  * @example-python Fetch markets
125
- * markets = exchange.fetch_markets(query='Trump', limit=20)
127
+ * markets = exchange.fetch_markets(query='Trump', limit=10000)
126
128
  * print(markets[0].title)
127
129
  *
128
130
  * @example-python Get market by slug
@@ -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,15 +22,17 @@ 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
- * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 20 });
28
+ * const markets = await exchange.fetchMarkets({ query: 'Trump', limit: 10000 });
27
29
  * console.log(markets[0].title);
28
30
  *
29
31
  * @example-ts Get market by slug
30
32
  * const markets = await exchange.fetchMarkets({ slug: 'will-trump-win' });
31
33
  *
32
34
  * @example-python Fetch markets
33
- * markets = exchange.fetch_markets(query='Trump', limit=20)
35
+ * markets = exchange.fetch_markets(query='Trump', limit=10000)
34
36
  * print(markets[0].title)
35
37
  *
36
38
  * @example-python Get market by slug
@@ -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 || 20;
51
88
  return unifiedEvents.slice(0, limit);
52
89
  }
53
90
  catch (error) {
@@ -138,7 +138,7 @@ async function fetchMarketsBySlug(eventTicker) {
138
138
  }
139
139
  async function searchMarkets(query, params) {
140
140
  // We must fetch ALL markets to search them locally since we don't have server-side search
141
- const searchLimit = 5000;
141
+ const searchLimit = 10000;
142
142
  const markets = await fetchMarketsDefault({ ...params, limit: searchLimit });
143
143
  const lowerQuery = query.toLowerCase();
144
144
  const searchIn = params?.searchIn || 'title'; // Default to title-only search
@@ -151,18 +151,18 @@ async function searchMarkets(query, params) {
151
151
  return descMatch;
152
152
  return titleMatch || descMatch; // 'both'
153
153
  });
154
- const limit = params?.limit || 20;
154
+ const limit = params?.limit || 10000;
155
155
  return filtered.slice(0, limit);
156
156
  }
157
157
  async function fetchMarketsDefault(params) {
158
- const limit = params?.limit || 50;
158
+ const limit = params?.limit || 10000;
159
159
  const offset = params?.offset || 0;
160
160
  const now = Date.now();
161
161
  const status = params?.status || 'active'; // Default to 'active'
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 || 20
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,10 +79,12 @@ 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,
50
- limit: params?.limit || 20,
87
+ limit: params?.limit || 10000,
51
88
  page: params?.page || 1,
52
89
  similarityThreshold: params?.similarityThreshold || 0.5
53
90
  }
@@ -71,10 +108,10 @@ async function searchMarkets(marketFetcher, query, params) {
71
108
  }
72
109
  return allMarkets
73
110
  .filter((m) => m !== null && m.outcomes.length > 0)
74
- .slice(0, params?.limit || 20);
111
+ .slice(0, params?.limit || 10000);
75
112
  }
76
113
  async function fetchMarketsDefault(marketFetcher, params) {
77
- const limit = params?.limit || 200;
114
+ const limit = params?.limit || 10000;
78
115
  const offset = params?.offset || 0;
79
116
  // Map sort parameter to SDK's sortBy
80
117
  let sortBy = 'lp_rewards';
@@ -4,41 +4,72 @@ exports.fetchEvents = fetchEvents;
4
4
  const utils_1 = require("./utils");
5
5
  const errors_1 = require("./errors");
6
6
  async function fetchEvents(params) {
7
- const searchLimit = 100000; // Fetch all events for comprehensive search
8
7
  try {
9
- const status = params?.status || 'active';
8
+ if (!params.query) {
9
+ // If no query is provided, we can't use the search endpoint effectively.
10
+ // However, the BaseExchange interface enforces query presence for fetchEvents.
11
+ // Just in case, we return empty or throw.
12
+ throw new Error("Query is required for Polymarket event search");
13
+ }
14
+ const limit = params.limit || 10000;
15
+ const status = params.status || 'active';
10
16
  const queryParams = {
11
- limit: searchLimit
17
+ q: params.query,
18
+ limit_per_type: 50,
19
+ sort: 'volume',
20
+ ascending: false
21
+ };
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);
12
25
  };
13
- if (status === 'active') {
14
- queryParams.active = 'true';
15
- queryParams.closed = 'false';
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
+ });
16
46
  }
17
- else if (status === 'closed') {
18
- queryParams.active = 'false';
19
- queryParams.closed = 'true';
47
+ else if (status === 'active') {
48
+ const rawEvents = await fetchWithStatus('active');
49
+ events = rawEvents.filter(filterActive);
20
50
  }
21
- else {
22
- // 'all' - no filter, maybe handled by default or API behavior
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);
23
56
  }
24
- // Fetch events from Gamma API using parallel pagination
25
- const events = await (0, utils_1.paginateParallel)(utils_1.GAMMA_API_URL, queryParams);
26
- // Client-side text filtering
27
- const lowerQuery = (params?.query || '').toLowerCase();
28
- const searchIn = params?.searchIn || 'title';
29
- const filtered = events.filter((event) => {
57
+ // Client-side filtering to ensure title matches (API does fuzzy search)
58
+ const lowerQuery = params.query.toLowerCase();
59
+ const searchIn = params.searchIn || 'title';
60
+ const filteredEvents = events.filter((event) => {
30
61
  const titleMatch = (event.title || '').toLowerCase().includes(lowerQuery);
31
62
  const descMatch = (event.description || '').toLowerCase().includes(lowerQuery);
32
63
  if (searchIn === 'title')
33
64
  return titleMatch;
34
65
  if (searchIn === 'description')
35
66
  return descMatch;
36
- return titleMatch || descMatch;
67
+ return titleMatch || descMatch; // 'both'
37
68
  });
38
- // Map to UnifiedEvent
39
- const unifiedEvents = filtered.map((event) => {
69
+ // Map events to UnifiedEvent
70
+ const unifiedEvents = filteredEvents.map((event) => {
40
71
  const markets = [];
41
- if (event.markets) {
72
+ if (event.markets && Array.isArray(event.markets)) {
42
73
  for (const market of event.markets) {
43
74
  const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market, { useQuestionAsCandidateFallback: true });
44
75
  if (unifiedMarket) {
@@ -59,8 +90,6 @@ async function fetchEvents(params) {
59
90
  };
60
91
  return unifiedEvent;
61
92
  });
62
- // Apply limit to filtered results
63
- const limit = params?.limit || 20;
64
93
  return unifiedEvents.slice(0, limit);
65
94
  }
66
95
  catch (error) {
@@ -45,30 +45,48 @@ async function fetchMarketsBySlug(slug) {
45
45
  return unifiedMarkets;
46
46
  }
47
47
  async function searchMarkets(query, params) {
48
- const searchLimit = 5000; // Fetch enough markets for a good search pool
49
- // Fetch markets with a higher limit
50
- const markets = await fetchMarketsDefault({
51
- ...params,
52
- limit: searchLimit
53
- });
54
- // Client-side text filtering
48
+ const limit = params?.limit || 10000;
49
+ // Use parallel pagination to fetch all matching events
50
+ // Each event can contain multiple markets, so we need a larger pool
51
+ const queryParams = {
52
+ q: query,
53
+ limit_per_type: 50, // Fetch 50 events per page
54
+ events_status: params?.status === 'all' ? undefined : (params?.status === 'inactive' || params?.status === 'closed' ? 'closed' : 'active'),
55
+ sort: 'volume',
56
+ ascending: false
57
+ };
58
+ // Fetch events with parallel pagination
59
+ const events = await (0, utils_1.paginateSearchParallel)(utils_1.GAMMA_SEARCH_URL, queryParams, limit * 5);
60
+ const unifiedMarkets = [];
55
61
  const lowerQuery = query.toLowerCase();
56
- const searchIn = params?.searchIn || 'title'; // Default to title-only search
57
- const filtered = markets.filter(market => {
58
- const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
59
- const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
60
- if (searchIn === 'title')
61
- return titleMatch;
62
- if (searchIn === 'description')
63
- return descMatch;
64
- return titleMatch || descMatch; // 'both'
65
- });
66
- // Apply limit to filtered results
67
- const limit = params?.limit || 20;
68
- return filtered.slice(0, limit);
62
+ const searchIn = params?.searchIn || 'title';
63
+ // Flatten events into markets
64
+ for (const event of events) {
65
+ if (!event.markets)
66
+ continue;
67
+ for (const market of event.markets) {
68
+ const unifiedMarket = (0, utils_1.mapMarketToUnified)(event, market, { useQuestionAsCandidateFallback: true });
69
+ if (!unifiedMarket)
70
+ continue;
71
+ // Apply client-side filtering on market title
72
+ const titleMatch = (unifiedMarket.title || '').toLowerCase().includes(lowerQuery);
73
+ const descMatch = (unifiedMarket.description || '').toLowerCase().includes(lowerQuery);
74
+ let matches = false;
75
+ if (searchIn === 'title')
76
+ matches = titleMatch;
77
+ else if (searchIn === 'description')
78
+ matches = descMatch;
79
+ else
80
+ matches = titleMatch || descMatch;
81
+ if (matches) {
82
+ unifiedMarkets.push(unifiedMarket);
83
+ }
84
+ }
85
+ }
86
+ return unifiedMarkets.slice(0, limit);
69
87
  }
70
88
  async function fetchMarketsDefault(params) {
71
- const limit = params?.limit || 200; // Higher default for better coverage
89
+ const limit = params?.limit || 10000; // Higher default for better coverage
72
90
  const offset = params?.offset || 0;
73
91
  // Map generic sort params to Polymarket Gamma API params
74
92
  let queryParams = {
@@ -80,7 +98,7 @@ async function fetchMarketsDefault(params) {
80
98
  queryParams.active = 'true';
81
99
  queryParams.closed = 'false';
82
100
  }
83
- else if (status === 'closed') {
101
+ else if (status === 'closed' || status === 'inactive') {
84
102
  queryParams.active = 'false';
85
103
  queryParams.closed = 'true';
86
104
  }
@@ -1,5 +1,6 @@
1
1
  import { UnifiedMarket, CandleInterval } from '../../types';
2
2
  export declare const GAMMA_API_URL = "https://gamma-api.polymarket.com/events";
3
+ export declare const GAMMA_SEARCH_URL = "https://gamma-api.polymarket.com/public-search";
3
4
  export declare const CLOB_API_URL = "https://clob.polymarket.com";
4
5
  export declare const DATA_API_URL = "https://data-api.polymarket.com";
5
6
  export declare function mapMarketToUnified(event: any, market: any, options?: {
@@ -11,3 +12,8 @@ export declare function mapIntervalToFidelity(interval: CandleInterval): number;
11
12
  * Polymarket Gamma API has a hard limit of 500 results per request.
12
13
  */
13
14
  export declare function paginateParallel(url: string, params: any, maxResults?: number): Promise<any[]>;
15
+ /**
16
+ * Fetch all results from Gamma public-search API using parallel pagination.
17
+ * Uses 'page' parameter instead of 'offset'.
18
+ */
19
+ export declare function paginateSearchParallel(url: string, params: any, maxResults?: number): Promise<any[]>;
@@ -33,12 +33,14 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.DATA_API_URL = exports.CLOB_API_URL = exports.GAMMA_API_URL = void 0;
36
+ exports.DATA_API_URL = exports.CLOB_API_URL = exports.GAMMA_SEARCH_URL = exports.GAMMA_API_URL = void 0;
37
37
  exports.mapMarketToUnified = mapMarketToUnified;
38
38
  exports.mapIntervalToFidelity = mapIntervalToFidelity;
39
39
  exports.paginateParallel = paginateParallel;
40
+ exports.paginateSearchParallel = paginateSearchParallel;
40
41
  const market_utils_1 = require("../../utils/market-utils");
41
42
  exports.GAMMA_API_URL = 'https://gamma-api.polymarket.com/events';
43
+ exports.GAMMA_SEARCH_URL = 'https://gamma-api.polymarket.com/public-search';
42
44
  exports.CLOB_API_URL = 'https://clob.polymarket.com';
43
45
  exports.DATA_API_URL = 'https://data-api.polymarket.com';
44
46
  function mapMarketToUnified(event, market, options = {}) {
@@ -171,3 +173,42 @@ async function paginateParallel(url, params, maxResults = 10000) {
171
173
  }));
172
174
  return [firstPage, ...remainingPages].flat();
173
175
  }
176
+ /**
177
+ * Fetch all results from Gamma public-search API using parallel pagination.
178
+ * Uses 'page' parameter instead of 'offset'.
179
+ */
180
+ async function paginateSearchParallel(url, params, maxResults = 10000) {
181
+ const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
182
+ // 1. Fetch the first page to check pagination info
183
+ const firstPageResponse = await axios.get(url, {
184
+ params: { ...params, page: 1 }
185
+ });
186
+ const data = firstPageResponse.data;
187
+ const firstPageEvents = data.events || [];
188
+ const pagination = data.pagination;
189
+ // If no more pages, return what we have
190
+ if (!pagination?.hasMore || firstPageEvents.length === 0) {
191
+ return firstPageEvents;
192
+ }
193
+ // 2. Calculate how many pages to fetch based on totalResults and limit_per_type
194
+ const limitPerType = params.limit_per_type || 20;
195
+ const totalResults = Math.min(pagination.totalResults || 0, maxResults);
196
+ const totalPages = Math.ceil(totalResults / limitPerType);
197
+ // Fetch remaining pages in parallel
198
+ const pageNumbers = [];
199
+ for (let i = 2; i <= totalPages; i++) {
200
+ pageNumbers.push(i);
201
+ }
202
+ const remainingPages = await Promise.all(pageNumbers.map(async (pageNum) => {
203
+ try {
204
+ const res = await axios.get(url, {
205
+ params: { ...params, page: pageNum }
206
+ });
207
+ return res.data?.events || [];
208
+ }
209
+ catch (e) {
210
+ return []; // Swallow individual page errors to be robust
211
+ }
212
+ }));
213
+ return [firstPageEvents, ...remainingPages].flat();
214
+ }
@@ -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.0",
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.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.1.0,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"