kinetex 1.0.0-rc.2 → 1.1.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.
Files changed (51) hide show
  1. package/README.md +104 -16
  2. package/dist/browser/kinetex.esm.js +18 -18
  3. package/dist/browser/kinetex.js +509 -240
  4. package/dist/browser/kinetex.min.js +18 -18
  5. package/dist/cjs/client.js +180 -11
  6. package/dist/cjs/cookie-store.js +10 -0
  7. package/dist/cjs/core.js +25 -8
  8. package/dist/cjs/graphql.js +18 -4
  9. package/dist/cjs/logging.js +10 -2
  10. package/dist/cjs/mod.js +1 -1
  11. package/dist/cjs/pagination.js +6 -1
  12. package/dist/cjs/response.js +8 -6
  13. package/dist/cjs/sse.js +5 -2
  14. package/dist/cjs/utils.js +238 -57
  15. package/dist/cjs/ws.js +6 -1
  16. package/dist/esm/client.js +180 -11
  17. package/dist/esm/client.js.map +1 -1
  18. package/dist/esm/cookie-store.js +10 -0
  19. package/dist/esm/cookie-store.js.map +1 -1
  20. package/dist/esm/core.js +25 -8
  21. package/dist/esm/core.js.map +1 -1
  22. package/dist/esm/graphql.js +18 -4
  23. package/dist/esm/graphql.js.map +1 -1
  24. package/dist/esm/logging.js +10 -2
  25. package/dist/esm/logging.js.map +1 -1
  26. package/dist/esm/mod.js +1 -1
  27. package/dist/esm/mod.js.map +1 -1
  28. package/dist/esm/pagination.js +6 -1
  29. package/dist/esm/pagination.js.map +1 -1
  30. package/dist/esm/response.js +8 -6
  31. package/dist/esm/response.js.map +1 -1
  32. package/dist/esm/sse.js +5 -2
  33. package/dist/esm/sse.js.map +1 -1
  34. package/dist/esm/utils.js +238 -57
  35. package/dist/esm/utils.js.map +1 -1
  36. package/dist/esm/ws.js +6 -1
  37. package/dist/esm/ws.js.map +1 -1
  38. package/dist/types/client.d.ts.map +1 -1
  39. package/dist/types/cookie-store.d.ts.map +1 -1
  40. package/dist/types/core.d.ts.map +1 -1
  41. package/dist/types/graphql.d.ts.map +1 -1
  42. package/dist/types/logging.d.ts.map +1 -1
  43. package/dist/types/mod.d.ts +1 -1
  44. package/dist/types/mod.d.ts.map +1 -1
  45. package/dist/types/pagination.d.ts.map +1 -1
  46. package/dist/types/response.d.ts.map +1 -1
  47. package/dist/types/sse.d.ts.map +1 -1
  48. package/dist/types/utils.d.ts +18 -2
  49. package/dist/types/utils.d.ts.map +1 -1
  50. package/dist/types/ws.d.ts.map +1 -1
  51. package/package.json +6 -5
package/README.md CHANGED
@@ -155,7 +155,10 @@ const client = kinetex({
155
155
  rateLimit: { limit: 100, windowMs: 60_000, queue: true, maxQueue: 100 },
156
156
 
157
157
  // ── Proxy ──
158
- proxy: { url: "socks5://127.0.0.1:1080" },
158
+ // NOTE: `proxy` fails fast — kinetex's built-in transports cannot route
159
+ // through it. Use the `fetch` option with a proxy-capable agent for
160
+ // HTTP(S) proxies, or createSocks5Tunnel() from "kinetex/socks5" for SOCKS5.
161
+ // proxy: { url: "socks5://127.0.0.1:1080" }, // → throws with guidance
159
162
 
160
163
  // ── Cache ──
161
164
  cache: { storage: "memory", ttlMs: 60_000, maxEntries: 1000, swr: true },
@@ -262,10 +265,8 @@ const data = await client
262
265
  .headers({ "X-A": "1", "X-B": "2" }) // multiple headers
263
266
  .param("page", "1") // single query param
264
267
  .params({ limit: "10", sort: "name" }) // multiple params
265
- .query({ filter: "active" }) // alias for .params()
266
- .body("raw text") // raw body
268
+ .withBody("raw text") // raw body (string, Uint8Array, ReadableStream, ...)
267
269
  .withJSON({ key: "value" }) // JSON body (sets Content-Type)
268
- .withBody("text") // alias for .body()
269
270
  .withForm(formData) // FormData body
270
271
  .bearer("token") // Bearer auth
271
272
  .basic("user", "pass") // Basic auth
@@ -275,7 +276,7 @@ const data = await client
275
276
  .retry(3, { baseDelayMs: 1000 }) // Max retries + optional config
276
277
  .noRetry() // Skip retry
277
278
  .timeout(5000) // Timeout in ms
278
- .proxy({ url: "socks5://..." }) // Proxy config
279
+ .proxy({ url: "socks5://..." }) // Throws — see "Proxy" note above; use fetch+agent or createSocks5Tunnel()
279
280
  .cache({ ttlMs: 5000 }) // Cache config
280
281
  .noCache() // Force fresh fetch
281
282
  .maxSize(1_000_000) // Max response size
@@ -428,10 +429,9 @@ The `ctx` parameter in `shouldRetry` and `onRetry` is of type `RetryContext`:
428
429
  interface RetryContext {
429
430
  attempt: number;
430
431
  maxRetries: number;
431
- response?: KinetexResponse<unknown>;
432
- error?: unknown;
432
+ response: KinetexResponse<unknown> | null; // null when the attempt failed before a response
433
+ error: unknown;
433
434
  request: KinetexRequest;
434
- delayMs?: number;
435
435
  }
436
436
  ```
437
437
 
@@ -1319,6 +1319,23 @@ const sseClient = await client.sse("/events", {
1319
1319
  });
1320
1320
  ```
1321
1321
 
1322
+ ### SSEClient Lifecycle & Health
1323
+
1324
+ ```ts
1325
+ // Collect a bounded number of events (resolves or aborts)
1326
+ const events = await sse.collect({ limit: 100, signal: controller.signal });
1327
+
1328
+ // Health snapshot
1329
+ sse.url; // Current URL (updated after reconnect)
1330
+ sse.closed; // boolean
1331
+ sse.streamHealth;
1332
+ // { connected, totalEvents, totalReconnects, lastEventAt, lastEventId, reconnectAttempt }
1333
+
1334
+ // Teardown
1335
+ sse.close(); // Graceful close — stops reconnecting
1336
+ destroy(); // Hard teardown
1337
+ ```
1338
+
1322
1339
  ---
1323
1340
 
1324
1341
  ## WebSocket
@@ -1386,11 +1403,34 @@ interface WSMetrics {
1386
1403
  bytesReceived: number;
1387
1404
  reconnectCount: number;
1388
1405
  totalConnectAttempts: number;
1389
- uptimeMs: number | null;
1406
+ uptimeMs: number;
1390
1407
  }
1391
1408
 
1392
1409
  // Utility
1393
1410
  const ws = await connectWS("wss://api.example.com/ws", { onMessage: ... });
1411
+
1412
+ // Connection state & health
1413
+ ws.state; // "CONNECTING" | "OPEN" | "CLOSING" | "CLOSED" | "RECONNECTING"
1414
+ ws.connected; // boolean
1415
+ ws.bufferedCount; // Messages queued while disconnected
1416
+ ws.metrics; // WSMetrics (see above)
1417
+ await ws.waitForOpen(5000); // Resolve when OPEN (throws on timeout)
1418
+
1419
+ // Rooms (pub/sub groups — auto re-joined on reconnect when keepRooms: true)
1420
+ ws.join("prices");
1421
+ ws.join("orders", "v2"); // with optional namespace
1422
+ ws.leave("prices");
1423
+ ws.rooms; // readonly WSSubscribedRoom[]
1424
+
1425
+ // Backpressure
1426
+ ws.backpressure; // { bufferedBytes, highWaterMark, lowWaterMark, isBackpressured, ... }
1427
+ await ws.drain(30_000); // Wait until outbound buffer is flushed
1428
+ await ws.drainAndClose(30_000); // Drain, then close gracefully
1429
+ ws.drainBuffer(); // Take queued offline messages as an array
1430
+
1431
+ // Teardown
1432
+ ws.close(1000, "done"); // Close with code/reason (disconnects reconnect logic)
1433
+ ws.destroy(); // Hard teardown, no close frame
1394
1434
  ```
1395
1435
 
1396
1436
  ### Client-Level WebSocket
@@ -1761,8 +1801,33 @@ const customConnector: TcpConnector = socks5Connector({
1761
1801
  }); // Returns a TcpConnector function
1762
1802
 
1763
1803
  // Client-level proxy
1764
- kinetex({
1765
- proxy: { url: "socks5://127.0.0.1:1080", username: "user", password: "pass" },
1804
+ // NOTE: like the per-request option, client-level `proxy` fails fast with
1805
+ // guidance instead of silently routing direct. For SOCKS5 use createSocks5Tunnel():
1806
+ try {
1807
+ kinetex({
1808
+ proxy: { url: "socks5://127.0.0.1:1080", username: "user", password: "pass" },
1809
+ });
1810
+ } catch (e) {
1811
+ // KinetexError: "proxy is configured but kinetex's built-in transports cannot
1812
+ // route through it ..."
1813
+ }
1814
+
1815
+ // Correct way to route through an HTTP(S) proxy — supply a proxy-aware fetch:
1816
+ import { ProxyAgent } from "undici"; // npm i undici (Node.js)
1817
+ const proxied = kinetex({
1818
+ fetch: new ProxyAgent("http://127.0.0.1:8080").dispatch.bind(new ProxyAgent("http://127.0.0.1:8080")) as typeof fetch,
1819
+ });
1820
+
1821
+ // Correct way to route through a SOCKS5 proxy — create a tunnel transport:
1822
+ import { createSocks5Tunnel } from "kinetex/socks5";
1823
+ const tunnel = createSocks5Tunnel({ proxyHost: "127.0.0.1", proxyPort: 1080 });
1824
+ const viaSocks = await tunnel.send({
1825
+ url: "https://api.example.com/data",
1826
+ method: "GET",
1827
+ headers: {},
1828
+ body: null,
1829
+ signal: null,
1830
+ meta: {},
1766
1831
  });
1767
1832
  ```
1768
1833
 
@@ -2493,6 +2558,17 @@ const result = parseUntrustedJSON(untrustedJson);
2493
2558
  // maxDepth: 16, maxStringLength: 1MB, maxArrayLength: 1000, maxObjectKeys: 100
2494
2559
  ```
2495
2560
 
2561
+ Also available: `sanitizeParsedJSON(value)` strips prototype-pollution keys
2562
+ (`__proto__`, `constructor`, `prototype`) from a value that was already parsed
2563
+ elsewhere (streaming parsers, legacy code paths).
2564
+
2565
+ > **Built-in protection:** kinetex applies `sanitizeParsedJSON` automatically
2566
+ > to every untrusted JSON body it parses — `readJSON`, `readNDJSON`,
2567
+ > `readJSONStream`, GraphQL responses/batch/SSE events, SSE `jsonSSE()` and
2568
+ > `SSERouter.onJSON()`, and WebSocket `message.json`. Hostile
2569
+ > `"__proto__": {...}` keys in server payloads can never reach user code or
2570
+ > downstream merges.
2571
+
2496
2572
  ---
2497
2573
 
2498
2574
  ## Type Guards & Utilities
@@ -2789,20 +2865,32 @@ import { kinetex } from "kinetex/browser";
2789
2865
  | Feature | Node 18+ | Node 22+ | Deno | Bun | Browser | CF Workers | Vercel Edge |
2790
2866
  | --------------------------- | -------- | -------- | --------- | --- | ------------ | ---------- | ----------- |
2791
2867
  | HTTP/1.1 fetch | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2792
- | HTTP/2 (fetch) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2868
+ | HTTP/2 (fetch, via Alt-Svc/runtime hints) | ✓* | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2793
2869
  | HTTP/2 (NodeHTTP2Transport) | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
2794
- | HTTP/3 (detection) | — | — | — | — | experimental | ✓ | — |
2795
- | WebSocket (WSClient) | ✓ | ✓ | ✓ | ✓ | ✓ | partial | ✗ |
2870
+ | HTTP/3 (detection via Alt-Svc) | ✓* | ✓* | ✓* | ✓* | experimental | ✓* | ✓* |
2871
+ | WebSocket (WSClient) | ✗¹ (no native WebSocket) | ✓ | ✓ | ✓ | ✓ | partial² | ✗³ |
2796
2872
  | SOCKS5 proxy | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
2797
2873
  | Blob | ✓ | ✓ | ✓ | ✓ | ✓ | guarded | guarded |
2798
2874
  | DOMException | ✓ | ✓ | ✓ | ✓ | ✓ | guarded | guarded |
2799
2875
  | Buffer | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
2800
2876
  | crypto.subtle | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2801
2877
  | ReadableStream | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2802
- | URLPattern | — | — | ✓ | — | ✓ | ✓ | — |
2803
- | Brotli decompression | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2878
+ | URL pattern matching | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2879
+ | Brotli decompression | ✓ | ✓ | ✗ passthrough | ✗ passthrough | ✗ passthrough | ✗ passthrough | ✗ passthrough |
2804
2880
  | Gzip/deflate decompression | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
2805
2881
 
2882
+ \* HTTP/2+ detection is best-effort: `detectHTTPVersion()` reports HTTP/2 only when the runtime exposes protocol evidence (response `httpVersion`/`protocol` properties, or an `Alt-Svc` header); otherwise it reports `HTTP/1.1`. This is accurate for Node 18's undici fetch, which does not negotiate h2 by default — use `NodeHTTP2Transport` (Node 22+) for guaranteed HTTP/2.
2883
+
2884
+ ¹ WSClient requires a native `WebSocket` constructor. Node added one in v22 — on Node 18 use a polyfill (`globalThis.WebSocket = require('undici').WebSocket`).
2885
+
2886
+ ² Cloudflare Workers exposes a `WebSocket` constructor, but outbound client connections depend on runtime support.
2887
+
2888
+ ³ Vercel Edge has no stable outbound `WebSocket` client API.
2889
+
2890
+ **URL pattern matching**: kinetex's `compilePattern` / `URLPattern` type (`kinetex/url`) is a built-in implementation — works identically in every runtime, does not use the native `URLPattern` API.
2891
+
2892
+ **Brotli**: `decompressStream` uses `node:zlib.createBrotliDecompress()` on Node.js only. On all other runtimes, brotli-encoded bodies pass through compressed (WHATWG `DecompressionStream` does not support brotli), surfacing as a parse error downstream — servers should not negotiate `br` for non-Node clients.
2893
+
2806
2894
  ---
2807
2895
 
2808
2896
  ## Resource Cleanup