kinetex 1.0.0 → 1.2.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.
- package/README.md +104 -16
- package/dist/browser/kinetex.esm.js +18 -18
- package/dist/browser/kinetex.js +581 -275
- package/dist/browser/kinetex.min.js +18 -18
- package/dist/cjs/client.js +180 -11
- package/dist/cjs/cookie-store.js +10 -0
- package/dist/cjs/core.js +53 -27
- package/dist/cjs/graphql.js +37 -8
- package/dist/cjs/interceptors.js +22 -11
- package/dist/cjs/logging.js +10 -2
- package/dist/cjs/mod.js +1 -1
- package/dist/cjs/pagination.js +6 -1
- package/dist/cjs/progress.js +13 -6
- package/dist/cjs/response.js +8 -6
- package/dist/cjs/sse.js +5 -2
- package/dist/cjs/utils.js +250 -57
- package/dist/cjs/ws.js +16 -2
- package/dist/esm/client.js +180 -11
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/cookie-store.js +10 -0
- package/dist/esm/cookie-store.js.map +1 -1
- package/dist/esm/core.js +53 -27
- package/dist/esm/core.js.map +1 -1
- package/dist/esm/graphql.js +37 -8
- package/dist/esm/graphql.js.map +1 -1
- package/dist/esm/interceptors.js +22 -11
- package/dist/esm/interceptors.js.map +1 -1
- package/dist/esm/logging.js +10 -2
- package/dist/esm/logging.js.map +1 -1
- package/dist/esm/mod.js +1 -1
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/pagination.js +6 -1
- package/dist/esm/pagination.js.map +1 -1
- package/dist/esm/progress.js +13 -6
- package/dist/esm/progress.js.map +1 -1
- package/dist/esm/response.js +8 -6
- package/dist/esm/response.js.map +1 -1
- package/dist/esm/sse.js +5 -2
- package/dist/esm/sse.js.map +1 -1
- package/dist/esm/utils.js +250 -57
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/ws.js +16 -2
- package/dist/esm/ws.js.map +1 -1
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/cookie-store.d.ts.map +1 -1
- package/dist/types/core.d.ts +4 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/graphql.d.ts.map +1 -1
- package/dist/types/interceptors.d.ts.map +1 -1
- package/dist/types/logging.d.ts.map +1 -1
- package/dist/types/mod.d.ts +1 -1
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/pagination.d.ts.map +1 -1
- package/dist/types/progress.d.ts.map +1 -1
- package/dist/types/response.d.ts.map +1 -1
- package/dist/types/sse.d.ts.map +1 -1
- package/dist/types/utils.d.ts +18 -2
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/types/ws.d.ts +4 -0
- package/dist/types/ws.d.ts.map +1 -1
- package/package.json +5 -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
|
-
|
|
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
|
-
.
|
|
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
|
|
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
|
|
432
|
-
error
|
|
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
|
|
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
|
-
|
|
1765
|
-
|
|
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)
|
|
2795
|
-
| WebSocket (WSClient) |
|
|
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
|
-
|
|
|
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
|