@limitless-exchange/sdk 1.0.2 → 1.0.3
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/README.md +42 -20
- package/dist/index.d.mts +296 -5
- package/dist/index.d.ts +296 -5
- package/dist/index.js +229 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -352,6 +352,42 @@ var HttpClient = class {
|
|
|
352
352
|
}
|
|
353
353
|
);
|
|
354
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Extracts a human-readable error message from API response payload.
|
|
357
|
+
* @internal
|
|
358
|
+
*/
|
|
359
|
+
extractErrorMessage(data, fallback) {
|
|
360
|
+
if (!data) {
|
|
361
|
+
return fallback;
|
|
362
|
+
}
|
|
363
|
+
if (typeof data === "object") {
|
|
364
|
+
if (Array.isArray(data.message)) {
|
|
365
|
+
const messages = data.message.map((err) => {
|
|
366
|
+
const details = Object.entries(err || {}).filter(([_key, val]) => val !== "" && val !== null && val !== void 0).map(([key, val]) => `${key}: ${val}`).join(", ");
|
|
367
|
+
return details || JSON.stringify(err);
|
|
368
|
+
}).filter((msg) => msg.trim() !== "").join(" | ");
|
|
369
|
+
return messages || data.error || JSON.stringify(data);
|
|
370
|
+
}
|
|
371
|
+
return data.message || data.error || data.msg || data.errors && JSON.stringify(data.errors) || JSON.stringify(data);
|
|
372
|
+
}
|
|
373
|
+
return String(data);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Creates a typed API error class from status code.
|
|
377
|
+
* @internal
|
|
378
|
+
*/
|
|
379
|
+
createTypedApiError(status, message, data, url, method) {
|
|
380
|
+
if (status === 429) {
|
|
381
|
+
return new RateLimitError(message, status, data, url, method);
|
|
382
|
+
}
|
|
383
|
+
if (status === 401 || status === 403) {
|
|
384
|
+
return new AuthenticationError(message, status, data, url, method);
|
|
385
|
+
}
|
|
386
|
+
if (status === 400) {
|
|
387
|
+
return new ValidationError(message, status, data, url, method);
|
|
388
|
+
}
|
|
389
|
+
return new APIError(message, status, data, url, method);
|
|
390
|
+
}
|
|
355
391
|
/**
|
|
356
392
|
* Sets the API key for authenticated requests.
|
|
357
393
|
*
|
|
@@ -377,6 +413,28 @@ var HttpClient = class {
|
|
|
377
413
|
const response = await this.client.get(url, config);
|
|
378
414
|
return response.data;
|
|
379
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Performs a GET request and returns raw response metadata.
|
|
418
|
+
*
|
|
419
|
+
* @remarks
|
|
420
|
+
* Use this when callers need access to status code or headers (e.g. redirect `Location`).
|
|
421
|
+
*
|
|
422
|
+
* @param url - Request URL
|
|
423
|
+
* @param config - Additional request configuration
|
|
424
|
+
* @returns Promise resolving to status, headers, and response data
|
|
425
|
+
*/
|
|
426
|
+
async getRaw(url, config) {
|
|
427
|
+
const response = await this.client.get(url, config);
|
|
428
|
+
if (response.status >= 400) {
|
|
429
|
+
const message = this.extractErrorMessage(response.data, `Request failed with status ${response.status}`);
|
|
430
|
+
throw this.createTypedApiError(response.status, message, response.data, url, "GET");
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
status: response.status,
|
|
434
|
+
headers: response.headers,
|
|
435
|
+
data: response.data
|
|
436
|
+
};
|
|
437
|
+
}
|
|
380
438
|
/**
|
|
381
439
|
* Performs a POST request.
|
|
382
440
|
*
|
|
@@ -1023,7 +1081,7 @@ var OrderValidationError = class extends Error {
|
|
|
1023
1081
|
}
|
|
1024
1082
|
};
|
|
1025
1083
|
function isFOKOrder(args) {
|
|
1026
|
-
return "
|
|
1084
|
+
return "makerAmount" in args;
|
|
1027
1085
|
}
|
|
1028
1086
|
function validateOrderArgs(args) {
|
|
1029
1087
|
if (!args.tokenId) {
|
|
@@ -1811,6 +1869,174 @@ var OrderClient = class {
|
|
|
1811
1869
|
}
|
|
1812
1870
|
};
|
|
1813
1871
|
|
|
1872
|
+
// src/market-pages/fetcher.ts
|
|
1873
|
+
var MAX_REDIRECT_DEPTH = 3;
|
|
1874
|
+
var MarketPageFetcher = class {
|
|
1875
|
+
/**
|
|
1876
|
+
* Creates a new market-pages fetcher.
|
|
1877
|
+
*
|
|
1878
|
+
* @param httpClient - HTTP client for API calls
|
|
1879
|
+
* @param logger - Optional logger
|
|
1880
|
+
*/
|
|
1881
|
+
constructor(httpClient, logger) {
|
|
1882
|
+
this.httpClient = httpClient;
|
|
1883
|
+
this.logger = logger || new NoOpLogger();
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Gets the navigation tree.
|
|
1887
|
+
*/
|
|
1888
|
+
async getNavigation() {
|
|
1889
|
+
this.logger.debug("Fetching navigation tree");
|
|
1890
|
+
return this.httpClient.get("/navigation");
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Resolves a market page by path.
|
|
1894
|
+
*
|
|
1895
|
+
* @remarks
|
|
1896
|
+
* Handles 301 redirects manually by re-requesting `/market-pages/by-path` with the
|
|
1897
|
+
* redirected path value from `Location` header.
|
|
1898
|
+
*/
|
|
1899
|
+
async getMarketPageByPath(path) {
|
|
1900
|
+
return this.getMarketPageByPathInternal(path, 0);
|
|
1901
|
+
}
|
|
1902
|
+
async getMarketPageByPathInternal(path, depth) {
|
|
1903
|
+
const query = new URLSearchParams({ path }).toString();
|
|
1904
|
+
const endpoint = `/market-pages/by-path?${query}`;
|
|
1905
|
+
const requestConfig = {
|
|
1906
|
+
maxRedirects: 0,
|
|
1907
|
+
validateStatus: (status) => status === 200 || status === 301
|
|
1908
|
+
};
|
|
1909
|
+
this.logger.debug("Resolving market page by path", { path, depth });
|
|
1910
|
+
const response = await this.httpClient.getRaw(endpoint, requestConfig);
|
|
1911
|
+
if (response.status === 200) {
|
|
1912
|
+
return response.data;
|
|
1913
|
+
}
|
|
1914
|
+
if (response.status !== 301) {
|
|
1915
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
1916
|
+
}
|
|
1917
|
+
if (depth >= MAX_REDIRECT_DEPTH) {
|
|
1918
|
+
throw new Error(
|
|
1919
|
+
`Too many redirects while resolving market page path '${path}' (max ${MAX_REDIRECT_DEPTH})`
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
const locationHeader = response.headers?.location;
|
|
1923
|
+
const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
|
|
1924
|
+
if (!location || typeof location !== "string") {
|
|
1925
|
+
throw new Error("Redirect response missing valid Location header");
|
|
1926
|
+
}
|
|
1927
|
+
const redirectedPath = this.extractRedirectPath(location);
|
|
1928
|
+
this.logger.info("Following market page redirect", {
|
|
1929
|
+
from: path,
|
|
1930
|
+
to: redirectedPath,
|
|
1931
|
+
depth: depth + 1
|
|
1932
|
+
});
|
|
1933
|
+
return this.getMarketPageByPathInternal(redirectedPath, depth + 1);
|
|
1934
|
+
}
|
|
1935
|
+
extractRedirectPath(location) {
|
|
1936
|
+
const directByPathPrefix = "/market-pages/by-path";
|
|
1937
|
+
if (location.startsWith(directByPathPrefix)) {
|
|
1938
|
+
const url = new URL(location, "https://api.limitless.exchange");
|
|
1939
|
+
const path = url.searchParams.get("path");
|
|
1940
|
+
if (!path) {
|
|
1941
|
+
throw new Error("Redirect location '/market-pages/by-path' is missing required 'path' query parameter");
|
|
1942
|
+
}
|
|
1943
|
+
return path;
|
|
1944
|
+
}
|
|
1945
|
+
if (/^https?:\/\//i.test(location)) {
|
|
1946
|
+
const url = new URL(location);
|
|
1947
|
+
if (url.pathname === directByPathPrefix) {
|
|
1948
|
+
const path = url.searchParams.get("path");
|
|
1949
|
+
if (!path) {
|
|
1950
|
+
throw new Error("Redirect location '/market-pages/by-path' is missing required 'path' query parameter");
|
|
1951
|
+
}
|
|
1952
|
+
return path;
|
|
1953
|
+
}
|
|
1954
|
+
return url.pathname || "/";
|
|
1955
|
+
}
|
|
1956
|
+
return location;
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Gets markets for a market page with optional filtering and pagination.
|
|
1960
|
+
*/
|
|
1961
|
+
async getMarkets(pageId, params = {}) {
|
|
1962
|
+
if (params.cursor !== void 0 && params.page !== void 0) {
|
|
1963
|
+
throw new Error("Parameters `cursor` and `page` are mutually exclusive");
|
|
1964
|
+
}
|
|
1965
|
+
const query = new URLSearchParams();
|
|
1966
|
+
if (params.page !== void 0) {
|
|
1967
|
+
query.append("page", String(params.page));
|
|
1968
|
+
}
|
|
1969
|
+
if (params.limit !== void 0) {
|
|
1970
|
+
query.append("limit", String(params.limit));
|
|
1971
|
+
}
|
|
1972
|
+
if (params.sort) {
|
|
1973
|
+
query.append("sort", params.sort);
|
|
1974
|
+
}
|
|
1975
|
+
if (params.cursor !== void 0) {
|
|
1976
|
+
query.append("cursor", params.cursor);
|
|
1977
|
+
}
|
|
1978
|
+
if (params.filters) {
|
|
1979
|
+
for (const [key, value] of Object.entries(params.filters)) {
|
|
1980
|
+
if (Array.isArray(value)) {
|
|
1981
|
+
for (const item of value) {
|
|
1982
|
+
query.append(key, this.stringifyFilterValue(item));
|
|
1983
|
+
}
|
|
1984
|
+
} else {
|
|
1985
|
+
query.append(key, this.stringifyFilterValue(value));
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
const queryString = query.toString();
|
|
1990
|
+
const endpoint = `/market-pages/${pageId}/markets${queryString ? `?${queryString}` : ""}`;
|
|
1991
|
+
this.logger.debug("Fetching market-page markets", { pageId, params });
|
|
1992
|
+
const response = await this.httpClient.get(endpoint);
|
|
1993
|
+
const markets = (response.data || []).map((marketData) => new Market(marketData, this.httpClient));
|
|
1994
|
+
if (response.pagination) {
|
|
1995
|
+
return {
|
|
1996
|
+
data: markets,
|
|
1997
|
+
pagination: response.pagination
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
if (response.cursor) {
|
|
2001
|
+
return {
|
|
2002
|
+
data: markets,
|
|
2003
|
+
cursor: response.cursor
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
throw new Error("Invalid market-page response: expected `pagination` or `cursor` metadata");
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Lists all property keys with options.
|
|
2010
|
+
*/
|
|
2011
|
+
async getPropertyKeys() {
|
|
2012
|
+
return this.httpClient.get("/property-keys");
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Gets a single property key by ID.
|
|
2016
|
+
*/
|
|
2017
|
+
async getPropertyKey(id) {
|
|
2018
|
+
return this.httpClient.get(`/property-keys/${id}`);
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Lists options for a property key, optionally filtered by parent option ID.
|
|
2022
|
+
*/
|
|
2023
|
+
async getPropertyOptions(keyId, parentId) {
|
|
2024
|
+
const query = new URLSearchParams();
|
|
2025
|
+
if (parentId) {
|
|
2026
|
+
query.append("parentId", parentId);
|
|
2027
|
+
}
|
|
2028
|
+
const queryString = query.toString();
|
|
2029
|
+
const endpoint = `/property-keys/${keyId}/options${queryString ? `?${queryString}` : ""}`;
|
|
2030
|
+
return this.httpClient.get(endpoint);
|
|
2031
|
+
}
|
|
2032
|
+
stringifyFilterValue(value) {
|
|
2033
|
+
if (typeof value === "boolean") {
|
|
2034
|
+
return value ? "true" : "false";
|
|
2035
|
+
}
|
|
2036
|
+
return String(value);
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
|
|
1814
2040
|
// src/websocket/client.ts
|
|
1815
2041
|
import { io } from "socket.io-client";
|
|
1816
2042
|
var WebSocketClient = class {
|
|
@@ -2193,6 +2419,7 @@ export {
|
|
|
2193
2419
|
HttpClient,
|
|
2194
2420
|
Market,
|
|
2195
2421
|
MarketFetcher,
|
|
2422
|
+
MarketPageFetcher,
|
|
2196
2423
|
NoOpLogger,
|
|
2197
2424
|
OrderBuilder,
|
|
2198
2425
|
OrderClient,
|