@cexyio/cexy 0.1.0-dev.2 → 0.1.0-dev.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 CHANGED
@@ -5,7 +5,7 @@ The official TypeScript/JavaScript SDK for the [CEXY.io](https://cexy.io) REST a
5
5
  - Typed models generated from the public OpenAPI spec ([cexy-api-spec](https://github.com/cexyio/cexy-api-spec)).
6
6
  - Safe by default: retries with backoff, order placement that never duplicates (via `client_order_id`), a client-side rate limiter.
7
7
  - A WebSocket client with heartbeat, reconnect and a live order book that applies the sync rules for you.
8
- - ESM and CommonJS, Node 20+, zero runtime dependencies.
8
+ - ESM and CommonJS, Node 22+, zero runtime dependencies.
9
9
 
10
10
  > **Status: 0.x.** The API is not yet frozen. It stays 0.x until the exchange ships HMAC request signing,
11
11
  > which will change how credentials are sent.
@@ -15,7 +15,7 @@ The official TypeScript/JavaScript SDK for the [CEXY.io](https://cexy.io) REST a
15
15
 
16
16
  ```bash
17
17
  npm install @cexyio/cexy@next
18
- # Node only, optional: lets the WebSocket client send a User-Agent (recommended before Node 22)
18
+ # Node only, optional: lets the WebSocket client send a User-Agent
19
19
  npm install ws
20
20
  ```
21
21
 
@@ -215,7 +215,7 @@ use public channels and poll REST for private state. If the session is revoked,
215
215
  `authLost`; public channels keep working.
216
216
 
217
217
  In Node the client uses the optional `ws` package when installed (so it can send the SDK User-Agent),
218
- otherwise the global `WebSocket` (Node 22+, browsers).
218
+ otherwise the global `WebSocket` (Node, browsers).
219
219
 
220
220
  ## Browsers
221
221
 
@@ -229,6 +229,9 @@ endpoints from a server. Browsers do not let scripts set `User-Agent`, so the SD
229
229
  - Credentials go only in the `X-API-Key` / `X-API-Secret` headers and only on private endpoints; never in URLs.
230
230
  - Only `https://` base URLs and `wss://` WebSocket URLs are accepted. `allowInsecure: true` permits
231
231
  `http://` / `ws://` solely for `localhost`, `127.0.0.1` or `::1` (local test servers).
232
+ - The SDK **never follows HTTP redirects**. A 3xx answer throws a `CexyApiError` with code
233
+ `UNEXPECTED_REDIRECT` (not retried), so credentials are never re-sent to another host and an order
234
+ is never re-posted to a redirect target. If you pass your own `fetch`, it must honour `redirect: "manual"`.
232
235
  - The SDK redacts the secret from `toString()`, `util.inspect`, `JSON.stringify` and error messages.
233
236
  - Keep keys in environment variables or a secret manager, not in code.
234
237
 
package/dist/index.cjs CHANGED
@@ -394,7 +394,13 @@ var Transport = class {
394
394
  let res;
395
395
  let text;
396
396
  try {
397
- res = await this.config.fetch(url.toString(), { method: info.method, headers, body, signal: ctrl.signal });
397
+ res = await this.config.fetch(url.toString(), {
398
+ method: info.method,
399
+ headers,
400
+ body,
401
+ signal: ctrl.signal,
402
+ redirect: "manual"
403
+ });
398
404
  text = await res.text();
399
405
  } catch (err) {
400
406
  if (opts.signal?.aborted) throw opts.signal.reason ?? err;
@@ -406,6 +412,14 @@ var Transport = class {
406
412
  clearTimeout(timer);
407
413
  opts.signal?.removeEventListener("abort", onAbort);
408
414
  }
415
+ if (res.type === "opaqueredirect" || res.status >= 300 && res.status < 400 || res.redirected) {
416
+ throw new CexyApiError({
417
+ status: res.status,
418
+ code: "UNEXPECTED_REDIRECT",
419
+ message: `${info.method} ${info.path}: the server answered with a redirect (HTTP ${res.status}); the SDK does not follow redirects. Check baseUrl.`,
420
+ retryable: false
421
+ });
422
+ }
409
423
  this.config.limiter?.update(res.headers);
410
424
  if (!res.ok) {
411
425
  const parsed = safeJson(text);
@@ -904,7 +918,7 @@ var TradingResource = class extends Resource {
904
918
  };
905
919
 
906
920
  // src/version.ts
907
- var VERSION = "0.1.0-dev.2";
921
+ var VERSION = "0.1.0-dev.3";
908
922
  var USER_AGENT = `cexy-typescript/${VERSION}`;
909
923
 
910
924
  // src/ws/emitter.ts
@@ -1664,7 +1678,7 @@ var CexyClient = class {
1664
1678
  assertSecureUrl(parsed, "https:", "http:", options.allowInsecure === true, "baseUrl");
1665
1679
  this.#allowInsecure = options.allowInsecure === true;
1666
1680
  const fetchImpl = options.fetch ?? globalThis.fetch;
1667
- if (!fetchImpl) throw new CexyConfigError("no global fetch found (Node 20+ required); pass options.fetch");
1681
+ if (!fetchImpl) throw new CexyConfigError("no global fetch found (Node 22+ required); pass options.fetch");
1668
1682
  const timeoutMs = options.timeoutMs ?? 1e4;
1669
1683
  const maxRetries = options.maxRetries ?? 3;
1670
1684
  if (!(timeoutMs > 0)) throw new CexyConfigError("timeoutMs must be > 0");
package/dist/index.d.cts CHANGED
@@ -3844,7 +3844,7 @@ interface CexyClientOptions {
3844
3844
  timeoutMs?: number;
3845
3845
  /** Retries after the first attempt for retryable failures. Default 3. */
3846
3846
  maxRetries?: number;
3847
- /** A `fetch` implementation. Default: the global `fetch` (Node 20+, browsers). */
3847
+ /** A `fetch` implementation. Default: the global `fetch` (Node 22+, browsers). */
3848
3848
  fetch?: FetchLike;
3849
3849
  /**
3850
3850
  * Client-side rate limit in requests per minute, or `false` to disable. Default 100/min
@@ -3925,9 +3925,9 @@ declare function isAmount(value: unknown): value is Amount;
3925
3925
  declare function assertAmountFields(body: Record<string, unknown>, fields: readonly string[], context: string): void;
3926
3926
 
3927
3927
  /** SDK version, kept in sync with package.json (a test enforces this). */
3928
- declare const VERSION = "0.1.0-dev.2";
3928
+ declare const VERSION = "0.1.0-dev.3";
3929
3929
  /** Default User-Agent product token. */
3930
- declare const USER_AGENT = "cexy-typescript/0.1.0-dev.2";
3930
+ declare const USER_AGENT = "cexy-typescript/0.1.0-dev.3";
3931
3931
 
3932
3932
  /** True for loopback hosts, the only ones where plain-text transport may be allowed. */
3933
3933
  declare function isLocalHost(hostname: string): boolean;
package/dist/index.d.ts CHANGED
@@ -3844,7 +3844,7 @@ interface CexyClientOptions {
3844
3844
  timeoutMs?: number;
3845
3845
  /** Retries after the first attempt for retryable failures. Default 3. */
3846
3846
  maxRetries?: number;
3847
- /** A `fetch` implementation. Default: the global `fetch` (Node 20+, browsers). */
3847
+ /** A `fetch` implementation. Default: the global `fetch` (Node 22+, browsers). */
3848
3848
  fetch?: FetchLike;
3849
3849
  /**
3850
3850
  * Client-side rate limit in requests per minute, or `false` to disable. Default 100/min
@@ -3925,9 +3925,9 @@ declare function isAmount(value: unknown): value is Amount;
3925
3925
  declare function assertAmountFields(body: Record<string, unknown>, fields: readonly string[], context: string): void;
3926
3926
 
3927
3927
  /** SDK version, kept in sync with package.json (a test enforces this). */
3928
- declare const VERSION = "0.1.0-dev.2";
3928
+ declare const VERSION = "0.1.0-dev.3";
3929
3929
  /** Default User-Agent product token. */
3930
- declare const USER_AGENT = "cexy-typescript/0.1.0-dev.2";
3930
+ declare const USER_AGENT = "cexy-typescript/0.1.0-dev.3";
3931
3931
 
3932
3932
  /** True for loopback hosts, the only ones where plain-text transport may be allowed. */
3933
3933
  declare function isLocalHost(hostname: string): boolean;
package/dist/index.js CHANGED
@@ -392,7 +392,13 @@ var Transport = class {
392
392
  let res;
393
393
  let text;
394
394
  try {
395
- res = await this.config.fetch(url.toString(), { method: info.method, headers, body, signal: ctrl.signal });
395
+ res = await this.config.fetch(url.toString(), {
396
+ method: info.method,
397
+ headers,
398
+ body,
399
+ signal: ctrl.signal,
400
+ redirect: "manual"
401
+ });
396
402
  text = await res.text();
397
403
  } catch (err) {
398
404
  if (opts.signal?.aborted) throw opts.signal.reason ?? err;
@@ -404,6 +410,14 @@ var Transport = class {
404
410
  clearTimeout(timer);
405
411
  opts.signal?.removeEventListener("abort", onAbort);
406
412
  }
413
+ if (res.type === "opaqueredirect" || res.status >= 300 && res.status < 400 || res.redirected) {
414
+ throw new CexyApiError({
415
+ status: res.status,
416
+ code: "UNEXPECTED_REDIRECT",
417
+ message: `${info.method} ${info.path}: the server answered with a redirect (HTTP ${res.status}); the SDK does not follow redirects. Check baseUrl.`,
418
+ retryable: false
419
+ });
420
+ }
407
421
  this.config.limiter?.update(res.headers);
408
422
  if (!res.ok) {
409
423
  const parsed = safeJson(text);
@@ -902,7 +916,7 @@ var TradingResource = class extends Resource {
902
916
  };
903
917
 
904
918
  // src/version.ts
905
- var VERSION = "0.1.0-dev.2";
919
+ var VERSION = "0.1.0-dev.3";
906
920
  var USER_AGENT = `cexy-typescript/${VERSION}`;
907
921
 
908
922
  // src/ws/emitter.ts
@@ -1662,7 +1676,7 @@ var CexyClient = class {
1662
1676
  assertSecureUrl(parsed, "https:", "http:", options.allowInsecure === true, "baseUrl");
1663
1677
  this.#allowInsecure = options.allowInsecure === true;
1664
1678
  const fetchImpl = options.fetch ?? globalThis.fetch;
1665
- if (!fetchImpl) throw new CexyConfigError("no global fetch found (Node 20+ required); pass options.fetch");
1679
+ if (!fetchImpl) throw new CexyConfigError("no global fetch found (Node 22+ required); pass options.fetch");
1666
1680
  const timeoutMs = options.timeoutMs ?? 1e4;
1667
1681
  const maxRetries = options.maxRetries ?? 3;
1668
1682
  if (!(timeoutMs > 0)) throw new CexyConfigError("timeoutMs must be > 0");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cexyio/cexy",
3
- "version": "0.1.0-dev.2",
3
+ "version": "0.1.0-dev.3",
4
4
  "description": "Official TypeScript/JavaScript SDK for the CEXY.io REST and WebSocket API",
5
5
  "license": "MIT",
6
6
  "author": "CEXY.io",
@@ -43,7 +43,7 @@
43
43
  ],
44
44
  "sideEffects": false,
45
45
  "engines": {
46
- "node": ">=20"
46
+ "node": ">=22"
47
47
  },
48
48
  "scripts": {
49
49
  "generate": "node scripts/generate.mjs",
@@ -65,16 +65,16 @@
65
65
  }
66
66
  },
67
67
  "devDependencies": {
68
- "@eslint/js": "^9.39.0",
69
- "@types/node": "^20.19.0",
68
+ "@eslint/js": "^10.0.1",
69
+ "@types/node": "^22.20.4",
70
70
  "@types/ws": "^8.18.0",
71
- "eslint": "^9.39.0",
71
+ "eslint": "^10.11.0",
72
72
  "openapi-typescript": "^7.13.0",
73
73
  "tsup": "^8.5.0",
74
74
  "typescript": "~5.9.3",
75
75
  "typescript-eslint": "^8.70.0",
76
- "vite": "^6.4.0",
77
- "vitest": "^3.2.7",
76
+ "vite": "^8.3.0",
77
+ "vitest": "^5.0.2",
78
78
  "ws": "^8.18.0"
79
79
  },
80
80
  "publishConfig": {