@hyperttp/core 1.5.5 → 2.0.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/Core/HyperCore.d.ts +176 -141
- package/Core/HyperCore.d.ts.map +1 -1
- package/Core/HyperCore.js +1 -1
- package/Core/HyperCore.js.map +3 -3
- package/README.md +260 -316
- package/defaultConfig.d.ts +9 -4
- package/defaultConfig.d.ts.map +1 -1
- package/defaultConfig.js +1 -1
- package/defaultConfig.js.map +2 -2
- package/index.d.ts +3 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +2 -2
- package/package.json +39 -33
- package/protocols/manager.d.ts +31 -0
- package/protocols/manager.d.ts.map +1 -0
- package/protocols/manager.js +2 -0
- package/protocols/manager.js.map +7 -0
- package/protocols/rest/index.d.ts +9 -0
- package/protocols/rest/index.d.ts.map +1 -0
- package/protocols/rest/index.js +2 -0
- package/protocols/rest/index.js.map +7 -0
- package/protocols/rest/receiver.d.ts +31 -0
- package/protocols/rest/receiver.d.ts.map +1 -0
- package/protocols/rest/receiver.js +2 -0
- package/protocols/rest/receiver.js.map +7 -0
- package/protocols/rest/sender.d.ts +19 -0
- package/protocols/rest/sender.d.ts.map +1 -0
- package/protocols/rest/sender.js +2 -0
- package/protocols/rest/sender.js.map +7 -0
- package/protocols/rest/type.d.ts +71 -0
- package/protocols/rest/type.d.ts.map +1 -0
- package/protocols/rest/type.js +2 -0
- package/protocols/rest/type.js.map +7 -0
- package/rest.d.ts +2 -0
- package/rest.d.ts.map +1 -0
- package/rest.js +2 -0
- package/rest.js.map +7 -0
- package/transports/fetch.d.ts +24 -0
- package/transports/fetch.d.ts.map +1 -0
- package/transports/fetch.js +2 -0
- package/transports/fetch.js.map +7 -0
- package/transports/manager.d.ts +17 -80
- package/transports/manager.d.ts.map +1 -1
- package/transports/manager.js +1 -6
- package/transports/manager.js.map +3 -3
- package/utils/abort.d.ts +23 -0
- package/utils/abort.d.ts.map +1 -0
- package/utils/abort.js +2 -0
- package/utils/abort.js.map +7 -0
- package/utils/errors.d.ts +12 -2
- package/utils/errors.d.ts.map +1 -1
- package/utils/errors.js +1 -1
- package/utils/errors.js.map +3 -3
- package/utils/merge.d.ts.map +1 -1
- package/utils/merge.js +1 -1
- package/utils/merge.js.map +2 -2
- package/Core/RequestBuilder.d.ts +0 -32
- package/Core/RequestBuilder.d.ts.map +0 -1
- package/Core/RequestBuilder.js +0 -2
- package/Core/RequestBuilder.js.map +0 -7
- package/transports/browser.d.ts +0 -37
- package/transports/browser.d.ts.map +0 -1
- package/transports/browser.js +0 -2
- package/transports/browser.js.map +0 -7
- package/transports/node.d.ts +0 -33
- package/transports/node.d.ts.map +0 -1
- package/transports/node.js +0 -2
- package/transports/node.js.map +0 -7
- package/utils/normalize.d.ts +0 -35
- package/utils/normalize.d.ts.map +0 -1
- package/utils/normalize.js +0 -2
- package/utils/normalize.js.map +0 -7
- package/utils/pipeline.d.ts +0 -63
- package/utils/pipeline.d.ts.map +0 -1
- package/utils/pipeline.js +0 -2
- package/utils/pipeline.js.map +0 -7
- package/utils/response.d.ts +0 -113
- package/utils/response.d.ts.map +0 -1
- package/utils/response.js +0 -2
- package/utils/response.js.map +0 -7
- package/utils/retryUtils.d.ts +0 -17
- package/utils/retryUtils.d.ts.map +0 -1
- package/utils/retryUtils.js +0 -2
- package/utils/retryUtils.js.map +0 -7
package/README.md
CHANGED
|
@@ -1,419 +1,363 @@
|
|
|
1
|
-
# @hyperttp/core
|
|
1
|
+
# @hyperttp/core
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Protocol-agnostic execution core for client and server communication.
|
|
4
4
|
|
|
5
|
+
**English** | [Русский](./lang/ru/README.md)
|
|
6
|
+
|
|
7
|
+
[](https://github.com/IT-IF-OR/hyperttp-core/actions/workflows/ci.yml)
|
|
5
8
|
[](https://www.npmjs.com/package/@hyperttp/core)
|
|
6
9
|
[](https://www.npmjs.com/package/@hyperttp/core)
|
|
7
10
|
[](https://bundlephobia.com/package/@hyperttp/core)
|
|
8
11
|
[](./LICENSE)
|
|
9
12
|
[](https://www.typescriptlang.org/)
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- **⚡ Zero-Overhead Hot Paths:** Fast header merging, response mapping, and object creation loops optimized for high-throughput environments. Object pooling for `InternalRequest` eliminates allocations in the hot path.
|
|
40
|
-
- **📍 LRU URL Caching:** Built-in string URL parsing cache (up to 512 entries) with automatic eviction to bypass repetitive `new URL()` overhead.
|
|
41
|
-
- **🔀 Intelligent Isomorphism:** Automatically switches between optimal underlying transports (Native Bun, Undici, or Node.js native `fetch`) while maintaining a unified, clean API.
|
|
42
|
-
- **🔌 Multi-Stage Hooks & Pipelines:** Granular control over the request lifecycle with sorted hook priorities and short-circuit capabilities.
|
|
43
|
-
- **🗜️ Transparent Decompression:** Automatic out-of-the-box handling of `gzip`, `deflate`, and `br` (Brotli) content-encodings for both standard `Uint8Array` buffers and `ReadableStream` data.
|
|
44
|
-
- **💎 Prototype Preservation:** Safe internal mapping that respects and carries forward custom prototypes passed via request configurations.
|
|
45
|
-
- **🛡️ Security Hardening:** Response body size limiting (`maxResponseBytes`), CRLF injection prevention in headers, SSL certificate validation enforcement, and concurrent request throttling (`maxConcurrent`).
|
|
46
|
-
- **📦 Tiny Footprint:** ~30.7 KB raw / ~9.6 KB gzip after minification — no unnecessary dependencies.
|
|
47
|
-
- **🦀 Rust-Powered Toolchain:** Built and linted with OXC (`oxlint` + `oxfmt`) for blazing-fast development cycles.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 📦 Ecosystem Packages
|
|
52
|
-
|
|
53
|
-
| Package | Description | Repository |
|
|
54
|
-
| ---------------------------------------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------- |
|
|
55
|
-
| **`@hyperttp/core`** (you are here) | Low-level core with transport pipeline | [GitHub](https://github.com/IT-IF-OR/hyperttp-core) |
|
|
56
|
-
| [`@hyperttp/types`](https://www.npmjs.com/package/@hyperttp/types) | Shared TypeScript type definitions | [GitHub](https://github.com/dirold2/hyperttp-types) |
|
|
57
|
-
| [`@hyperttp/transport-bun`](https://www.npmjs.com/package/@hyperttp/transport-bun) | Native Bun transport | [GitHub](https://github.com/IT-IF-OR/hyperttp-transport-bun) |
|
|
58
|
-
| [`@hyperttp/transport-undici`](https://www.npmjs.com/package/@hyperttp/transport-undici) | Undici transport for Node.js | [GitHub](https://github.com/IT-IF-OR/hyperttp-transport-undici) |
|
|
59
|
-
| [`hyperttp`](https://www.npmjs.com/package/hyperttp) | High-level client with all plugins pre-wired | [GitHub](https://github.com/dirold2/hyperttp) |
|
|
14
|
+
## Positioning
|
|
15
|
+
|
|
16
|
+
`@hyperttp/core` is a small orchestration kernel, not another wrapper around `fetch` and not a
|
|
17
|
+
batteries-included HTTP framework. It connects three independent extension points:
|
|
18
|
+
|
|
19
|
+
- **Protocols** define communication semantics. A protocol can send through a `HyperSender`,
|
|
20
|
+
receive through a `HyperReceiver`, or implement both roles as a `HyperProtocol`.
|
|
21
|
+
- **Transports** own physical I/O. The current transport contract executes outgoing requests and can
|
|
22
|
+
additionally listen for incoming requests.
|
|
23
|
+
- **Plugins** add client request policy and cross-cutting behavior without expanding the core.
|
|
24
|
+
|
|
25
|
+
The core owns dispatch, lifecycle, plugin execution and resource coordination. REST is included as
|
|
26
|
+
the baseline protocol; additional protocols, optimized transports and higher-level behavior live in
|
|
27
|
+
separate packages.
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Application
|
|
31
|
+
│
|
|
32
|
+
├── Plugins: retry, cache, auth, tracing, metrics, logging, policy
|
|
33
|
+
│
|
|
34
|
+
HyperCore
|
|
35
|
+
│
|
|
36
|
+
├── Protocol: prepare → send → parse
|
|
37
|
+
│ receive → handle → respond
|
|
38
|
+
│
|
|
39
|
+
└── Transport: execute (client) / listen (server)
|
|
40
|
+
Node.js / Bun / Deno / Browser / custom runtime
|
|
41
|
+
```
|
|
60
42
|
|
|
61
|
-
|
|
43
|
+
This boundary is intentional. New client-side retries, caches, authentication strategies,
|
|
44
|
+
observability tools and other request policies should be plugins. New wire semantics should be
|
|
45
|
+
protocol packages. New I/O implementations should be transport packages.
|
|
62
46
|
|
|
63
|
-
|
|
47
|
+
> Looking for a preconfigured HTTP client? Use
|
|
48
|
+
> [`hyperttp`](https://www.npmjs.com/package/hyperttp), which composes the core with application-level
|
|
49
|
+
> plugins and defaults.
|
|
64
50
|
|
|
65
|
-
|
|
51
|
+
## Core properties
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
│ └─ NodeTransport (global fetch) │
|
|
77
|
-
│ │ │
|
|
78
|
-
│ 3. Response Data Pipeline (onResponseData) │
|
|
79
|
-
│ │ │
|
|
80
|
-
│ 4. Internal Processing (decompression, mapping) │
|
|
81
|
-
│ │ │
|
|
82
|
-
│ 5. Response Pipeline (onResponse) │
|
|
83
|
-
│ ├─ Mutators (sync/async modification) │
|
|
84
|
-
│ └─ Side Effects (background, non-blocking) │
|
|
85
|
-
│ │ │
|
|
86
|
-
│ 6. Error Pipeline (onError) ─── recovery capable │
|
|
87
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
88
|
-
```
|
|
53
|
+
- Client and server orchestration.
|
|
54
|
+
- Protocol-independent request envelope and universal response shape.
|
|
55
|
+
- Client lifecycle: `prepare → send → parse`.
|
|
56
|
+
- Server lifecycle: `receive → handle → respond`.
|
|
57
|
+
- Per-protocol transport resolution and coordinated transport ownership.
|
|
58
|
+
- Blocking and background client plugin hooks.
|
|
59
|
+
- Runtime-aware transport selection with a browser-safe fetch fallback.
|
|
60
|
+
- Strict TypeScript contracts and module augmentation for typed protocol namespaces.
|
|
61
|
+
- No bundled runtime dependencies.
|
|
89
62
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
## 🚀 Quick Start
|
|
93
|
-
|
|
94
|
-
### Installation
|
|
63
|
+
## Installation
|
|
95
64
|
|
|
96
65
|
```bash
|
|
97
|
-
npm install @hyperttp/core
|
|
98
|
-
|
|
99
|
-
# Optional: install a specific transport
|
|
100
|
-
npm install @hyperttp/transport-bun # for Bun
|
|
101
|
-
npm install @hyperttp/transport-undici # for Node.js
|
|
66
|
+
npm install @hyperttp/core @hyperttp/types
|
|
102
67
|
```
|
|
103
68
|
|
|
104
|
-
|
|
69
|
+
Compatible optimized transports can be installed separately. Ensure the selected transport release
|
|
70
|
+
declares compatibility with `@hyperttp/types@^0.3.0`; older transport releases target the v1 type
|
|
71
|
+
contracts and cannot be installed alongside core 2.0. Without an optional transport package, the
|
|
72
|
+
core uses its built-in `FetchTransport` fallback.
|
|
105
73
|
|
|
106
|
-
|
|
74
|
+
## Client quick start
|
|
107
75
|
|
|
108
|
-
```
|
|
76
|
+
```ts
|
|
109
77
|
import { HyperCore } from "@hyperttp/core";
|
|
110
78
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
},
|
|
79
|
+
const core = new HyperCore();
|
|
80
|
+
|
|
81
|
+
const response = await core.rest.get("https://example.com/users", {
|
|
82
|
+
query: { page: 1 },
|
|
116
83
|
});
|
|
117
84
|
|
|
118
|
-
// Returns HttpResponse<T> with full control
|
|
119
|
-
const response = await http.get("https://api.example.com/data");
|
|
120
85
|
console.log(response.status);
|
|
121
|
-
console.log(response.
|
|
86
|
+
console.log(response.data);
|
|
122
87
|
|
|
123
|
-
|
|
124
|
-
const json = await response.json<User>();
|
|
125
|
-
const text = await response.text();
|
|
126
|
-
const buffer = await response.arrayBuffer();
|
|
127
|
-
|
|
128
|
-
// Shortcut methods
|
|
129
|
-
const userJson = await http.json<{ name: string }>("/users/123");
|
|
130
|
-
const logsText = await http.text("/logs/latest");
|
|
131
|
-
|
|
132
|
-
// Safely consume/drain a response without allocations
|
|
133
|
-
await http.dump("/analytics/ping");
|
|
88
|
+
await core.destroy();
|
|
134
89
|
```
|
|
135
90
|
|
|
136
|
-
|
|
91
|
+
The same request can use the protocol-independent API:
|
|
137
92
|
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
stream: true,
|
|
93
|
+
```ts
|
|
94
|
+
const response = await core.send({
|
|
95
|
+
protocol: "rest",
|
|
96
|
+
input: {
|
|
97
|
+
method: "GET",
|
|
98
|
+
url: "https://example.com/users",
|
|
99
|
+
query: { page: 1 },
|
|
100
|
+
},
|
|
147
101
|
});
|
|
148
102
|
```
|
|
149
103
|
|
|
150
|
-
|
|
104
|
+
## Server quick start
|
|
151
105
|
|
|
152
|
-
|
|
106
|
+
A transport may also expose `listen()`. `HyperCore` connects it to the receiver side of the selected
|
|
107
|
+
protocol:
|
|
153
108
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
109
|
+
```ts
|
|
157
110
|
import { HyperCore } from "@hyperttp/core";
|
|
158
111
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
http.use({
|
|
162
|
-
name: "performance-logger",
|
|
163
|
-
priority: 100, // Sorted execution priority (higher runs first)
|
|
164
|
-
mode: "background", // Forces onResponse to run as a non-blocking side-effect
|
|
165
|
-
|
|
166
|
-
setup: (ctx) => {
|
|
167
|
-
// Fired immediately on registration
|
|
168
|
-
ctx.config.logger?.info?.("Plugin active");
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
onRequest: async (req, ctx) => {
|
|
172
|
-
req.meta.startTime = performance.now();
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
onResponse: async (res, req, ctx) => {
|
|
176
|
-
const duration = performance.now() - (req.meta.startTime as number);
|
|
177
|
-
console.log(`[${req.method}] ${req.url} -> ${res.status} (${duration.toFixed(2)}ms)`);
|
|
178
|
-
},
|
|
112
|
+
const core = new HyperCore();
|
|
179
113
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
114
|
+
const server = await core.listen({
|
|
115
|
+
protocol: "rest",
|
|
116
|
+
host: "127.0.0.1",
|
|
117
|
+
port: 3000,
|
|
118
|
+
handler(request) {
|
|
119
|
+
return {
|
|
120
|
+
status: 200,
|
|
121
|
+
body: {
|
|
122
|
+
method: request.method,
|
|
123
|
+
path: request.path,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
183
126
|
},
|
|
184
127
|
});
|
|
128
|
+
|
|
129
|
+
// Closes active servers and releases every transport owned by this core.
|
|
130
|
+
await core.destroy();
|
|
185
131
|
```
|
|
186
132
|
|
|
187
|
-
|
|
133
|
+
Protocols can be client-only, server-only or expose both roles. The current transport contract always
|
|
134
|
+
provides client `execute()` and can additionally provide server `listen()`.
|
|
188
135
|
|
|
189
|
-
|
|
190
|
-
| ----------- | ---------------- | ------------------------------------------------------- |
|
|
191
|
-
| **REQUEST** | `onRequest` | Intercepts before execution. Supports short-circuiting. |
|
|
192
|
-
| **DATA** | `onResponseData` | Transforms raw transport responses before mapping. |
|
|
193
|
-
| **FORMAT** | `onResponse` | Modifies client-facing responses. |
|
|
194
|
-
| **ERROR** | `onError` | Catches and recovers from errors. |
|
|
136
|
+
## Protocols
|
|
195
137
|
|
|
196
|
-
|
|
138
|
+
REST is the only protocol implemented inside the core. External protocol modules are loaded lazily:
|
|
197
139
|
|
|
198
|
-
|
|
140
|
+
| Protocol | Package | Namespace |
|
|
141
|
+
| --------- | ------------------------------ | ---------------------------- |
|
|
142
|
+
| REST | built in | `core.rest` |
|
|
143
|
+
| GraphQL | `@hyperttp/protocol-graphql` | `core.graphql` |
|
|
144
|
+
| gRPC | `@hyperttp/protocol-grpc` | `core.grpc` |
|
|
145
|
+
| tRPC | `@hyperttp/protocol-trpc` | `core.trpc` |
|
|
146
|
+
| WebSocket | `@hyperttp/protocol-websocket` | `core.ws` / `core.websocket` |
|
|
147
|
+
| SSE | `@hyperttp/protocol-sse` | `core.sse` |
|
|
148
|
+
| MQTT | `@hyperttp/protocol-mqtt` | `core.mqtt` |
|
|
199
149
|
|
|
200
|
-
|
|
150
|
+
If an optional protocol package is missing, resolution fails with an installation hint instead of
|
|
151
|
+
silently selecting another protocol.
|
|
201
152
|
|
|
202
|
-
|
|
153
|
+
### Protocol roles
|
|
203
154
|
|
|
204
|
-
|
|
205
|
-
| ------------------- | ----------------- | ---------------------------------------- |
|
|
206
|
-
| **BunTransport** | Bun | `npm install @hyperttp/transport-bun` |
|
|
207
|
-
| **UndiciTransport** | Node.js | `npm install @hyperttp/transport-undici` |
|
|
208
|
-
| **NodeTransport** | Node.js / Browser | Built-in (uses global `fetch`) |
|
|
155
|
+
A unified protocol module can expose either or both roles:
|
|
209
156
|
|
|
210
|
-
|
|
157
|
+
```ts
|
|
158
|
+
const protocol = {
|
|
159
|
+
protocol: "my-protocol",
|
|
160
|
+
sender: mySender, // optional client role
|
|
161
|
+
receiver: myReceiver, // optional server role
|
|
162
|
+
};
|
|
211
163
|
|
|
212
|
-
|
|
164
|
+
core.registerProtocol(protocol);
|
|
165
|
+
```
|
|
213
166
|
|
|
214
|
-
|
|
215
|
-
|
|
167
|
+
A sender translates protocol input into a transport request and parses the transport response. A
|
|
168
|
+
receiver translates an incoming transport request into protocol input and serializes the application
|
|
169
|
+
response.
|
|
216
170
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const response = await fetch(req.url, {
|
|
220
|
-
method: req.method,
|
|
221
|
-
headers: req.headers,
|
|
222
|
-
body: req.body,
|
|
223
|
-
signal: req.signal,
|
|
224
|
-
});
|
|
171
|
+
Protocol packages can extend typed inputs and namespaces through `@hyperttp/types` module
|
|
172
|
+
augmentation:
|
|
225
173
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
url: response.url,
|
|
231
|
-
};
|
|
174
|
+
```ts
|
|
175
|
+
declare module "@hyperttp/types" {
|
|
176
|
+
interface ProtocolInputMap {
|
|
177
|
+
"my-protocol": MyProtocolInput;
|
|
232
178
|
}
|
|
233
179
|
|
|
234
|
-
|
|
235
|
-
|
|
180
|
+
interface HyperProtocols {
|
|
181
|
+
"my-protocol": MyProtocolMethods;
|
|
236
182
|
}
|
|
237
183
|
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Transports
|
|
187
|
+
|
|
188
|
+
A transport describes its client capabilities and optional server capability:
|
|
238
189
|
|
|
239
|
-
|
|
190
|
+
```ts
|
|
191
|
+
interface HyperTransport {
|
|
192
|
+
execute(request: TransportRequest): Promise<TransportResponse>;
|
|
193
|
+
listen?(options: TransportListenOptions): Promise<TransportServer>;
|
|
194
|
+
close?(): Promise<void> | void;
|
|
195
|
+
destroy?(): Promise<void> | void;
|
|
196
|
+
}
|
|
240
197
|
```
|
|
241
198
|
|
|
242
|
-
|
|
199
|
+
- `execute()` is the required client role.
|
|
200
|
+
- `listen()` is the optional server role.
|
|
201
|
+
- `protocols` or `supports()` declares protocol capabilities.
|
|
202
|
+
- The core resolves and retains transports per protocol and closes a shared instance only after its
|
|
203
|
+
last owner releases it.
|
|
243
204
|
|
|
244
|
-
|
|
205
|
+
Runtime selection:
|
|
245
206
|
|
|
246
|
-
|
|
207
|
+
| Runtime | Preferred package | Fallback |
|
|
208
|
+
| -------------- | ---------------------------- | ------------------------- |
|
|
209
|
+
| Node.js | `@hyperttp/transport-undici` | built-in `FetchTransport` |
|
|
210
|
+
| Bun | `@hyperttp/transport-bun` | built-in `FetchTransport` |
|
|
211
|
+
| Deno | `@hyperttp/transport-deno` | built-in `FetchTransport` |
|
|
212
|
+
| Browser / edge | custom transport | built-in `FetchTransport` |
|
|
247
213
|
|
|
248
|
-
|
|
249
|
-
|---|---|---|
|
|
250
|
-
| **SSL Validation** | `rejectUnauthorized` enforced via `https.Agent` in NodeTransport | `true` |
|
|
251
|
-
| **Response Size Limit** | `maxResponseBytes` — streaming transform aborts oversized responses | unlimited (0) |
|
|
252
|
-
| **CRLF Injection** | `\r\n` stripped from all header values in `appendHeader()` | always on |
|
|
253
|
-
| **Concurrency Throttle** | `maxConcurrent` — semaphore gates transport execution | unlimited (0) |
|
|
254
|
-
| **Transport Whitelist** | only explicitly registered packages can be loaded dynamically | strict |
|
|
255
|
-
| **Prototype Pollution** | all internal maps use `Object.create(null)` | always on |
|
|
214
|
+
A transport can also be supplied explicitly:
|
|
256
215
|
|
|
257
|
-
|
|
216
|
+
```ts
|
|
217
|
+
import { HyperCore } from "@hyperttp/core";
|
|
218
|
+
import { UndiciTransport } from "@hyperttp/transport-undici";
|
|
258
219
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
network: {
|
|
262
|
-
maxResponseBytes: 10 * 1024 * 1024, // 10 MB limit
|
|
263
|
-
maxConcurrent: 50, // max 50 in-flight requests
|
|
264
|
-
rejectUnauthorized: true,
|
|
265
|
-
},
|
|
220
|
+
const core = new HyperCore({
|
|
221
|
+
customTransport: new UndiciTransport(),
|
|
266
222
|
});
|
|
267
223
|
```
|
|
268
224
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
## 🔄 Extending Configuration
|
|
225
|
+
## Plugins
|
|
272
226
|
|
|
273
|
-
|
|
227
|
+
Plugins are the intended extension mechanism for client request behavior that does not belong to
|
|
228
|
+
protocol semantics or physical I/O. The current hooks run in the client `send()` lifecycle:
|
|
274
229
|
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
230
|
+
```ts
|
|
231
|
+
core.use({
|
|
232
|
+
name: "request-logger",
|
|
233
|
+
phase: "DATA",
|
|
234
|
+
onRequest(request) {
|
|
235
|
+
console.log("request", request.protocol);
|
|
236
|
+
},
|
|
237
|
+
onResponse(response) {
|
|
238
|
+
console.log("response", response.status);
|
|
239
|
+
},
|
|
240
|
+
onError(error) {
|
|
241
|
+
console.error(error);
|
|
242
|
+
},
|
|
279
243
|
});
|
|
280
244
|
```
|
|
281
245
|
|
|
282
|
-
|
|
246
|
+
Hooks:
|
|
283
247
|
|
|
284
|
-
|
|
248
|
+
- `onRequest` can modify a request or return an early response.
|
|
249
|
+
- `onResponse` can inspect or replace a response.
|
|
250
|
+
- `onError` can recover by returning a response.
|
|
251
|
+
- `mode: "background"` detaches response-side work from the blocking path.
|
|
252
|
+
- `enabled(config)` controls registration.
|
|
253
|
+
- `setup(context)` initializes a plugin.
|
|
285
254
|
|
|
286
|
-
|
|
287
|
-
// Performs a graceful or rapid shutdown sequence across transport selectors
|
|
288
|
-
await http.destroy(true);
|
|
289
|
-
```
|
|
255
|
+
Examples of functionality that belongs in plugins:
|
|
290
256
|
|
|
291
|
-
|
|
257
|
+
- retries and circuit breakers;
|
|
258
|
+
- caching and request deduplication;
|
|
259
|
+
- authentication and request signing;
|
|
260
|
+
- tracing, metrics and structured logging;
|
|
261
|
+
- rate limiting, concurrency control and scheduling;
|
|
262
|
+
- schema validation and application-specific policies.
|
|
292
263
|
|
|
293
|
-
##
|
|
264
|
+
## Built-in REST protocol
|
|
294
265
|
|
|
295
|
-
|
|
266
|
+
The REST package exposes client and server roles through `RestProtocol`.
|
|
296
267
|
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
268
|
+
```ts
|
|
269
|
+
const getResponse = await core.rest.get("/users", {
|
|
270
|
+
query: { page: 1, tag: ["a", "b"] },
|
|
271
|
+
headers: { accept: "application/json" },
|
|
272
|
+
timeout: 5_000,
|
|
273
|
+
});
|
|
303
274
|
|
|
304
|
-
|
|
275
|
+
const postResponse = await core.rest.post("/users", {
|
|
276
|
+
name: "Ada",
|
|
277
|
+
});
|
|
305
278
|
|
|
306
|
-
|
|
307
|
-
bun run bench.ts && npx tsx bench.ts
|
|
279
|
+
const streamResponse = await core.rest.stream("/events");
|
|
308
280
|
```
|
|
309
281
|
|
|
310
|
-
|
|
282
|
+
REST behavior includes:
|
|
311
283
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
| 🥈 2 | node-fetch | 21.58K | 9.23 ms | 9.39 ms | 12.86 ms | 14.72 ms | 0 |
|
|
320
|
-
| 🥉 3 | undici | 20.28K | 9.83 ms | 10.12 ms | 13.80 ms | 15.35 ms | 0 |
|
|
321
|
-
| 4 | **@hyperttp/core** | **17.39K** | **11.46 ms** | **12.94 ms** | **15.53 ms** | **16.66 ms** | 0 |
|
|
322
|
-
| 5 | ky | 12.80K | 15.58 ms | 13.95 ms | 20.62 ms | 22.77 ms | 0 |
|
|
323
|
-
| 6 | request | 8.31K | 24.00 ms | 23.43 ms | 29.54 ms | 32.82 ms | 0 |
|
|
324
|
-
| 7 | superagent | 7.96K | 25.09 ms | 24.95 ms | 26.85 ms | 28.46 ms | 0 |
|
|
325
|
-
| 8 | axios | 6.19K | 32.21 ms | 31.93 ms | 34.15 ms | 39.09 ms | 0 |
|
|
326
|
-
| 9 | got | 5.13K | 38.94 ms | 30.99 ms | 58.61 ms | 63.14 ms | 0 |
|
|
327
|
-
|
|
328
|
-
## 🟦 Bun 1.3.14 — NodeTransport
|
|
329
|
-
|
|
330
|
-
| Rank | Client | RPS | Avg | p50 | p90 | p99 | Errors |
|
|
331
|
-
| ---- | ------------------ | ---------- | ------------ | ------------ | ------------ | ------------ | ------ |
|
|
332
|
-
| 🥇 1 | bun-fetch | 23.94K | 8.32 ms | 8.63 ms | 10.64 ms | 13.77 ms | 0 |
|
|
333
|
-
| 🥈 2 | node-fetch | 21.71K | 9.18 ms | 9.35 ms | 12.58 ms | 14.40 ms | 0 |
|
|
334
|
-
| 🥉 3 | undici | 20.22K | 9.86 ms | 10.15 ms | 13.65 ms | 15.41 ms | 0 |
|
|
335
|
-
| 4 | **@hyperttp/core** | **17.80K** | **11.20 ms** | **12.38 ms** | **15.20 ms** | **17.29 ms** | 0 |
|
|
336
|
-
| 5 | ky | 12.78K | 15.63 ms | 13.92 ms | 20.59 ms | 23.70 ms | 0 |
|
|
337
|
-
| 6 | request | 8.20K | 24.31 ms | 23.62 ms | 29.13 ms | 34.53 ms | 0 |
|
|
338
|
-
| 7 | superagent | 8.06K | 24.79 ms | 24.79 ms | 27.26 ms | 29.52 ms | 0 |
|
|
339
|
-
| 8 | axios | 6.28K | 31.72 ms | 31.81 ms | 33.53 ms | 35.65 ms | 0 |
|
|
340
|
-
| 9 | got | 5.17K | 38.56 ms | 30.85 ms | 58.12 ms | 61.60 ms | 0 |
|
|
341
|
-
|
|
342
|
-
## 🟦 Node.js v24.14.1 — UndiciTransport
|
|
284
|
+
- query serialization with repeated array parameters;
|
|
285
|
+
- header normalization;
|
|
286
|
+
- JSON serialization for plain object and array request bodies;
|
|
287
|
+
- JSON, text and binary response handling;
|
|
288
|
+
- timeout and user abort propagation;
|
|
289
|
+
- unbuffered stream mode;
|
|
290
|
+
- request decoding and response serialization for the server role.
|
|
343
291
|
|
|
344
|
-
|
|
345
|
-
npm install @hyperttp/transport-undici
|
|
346
|
-
```
|
|
292
|
+
REST-specific public types are available from `@hyperttp/core/rest`.
|
|
347
293
|
|
|
348
|
-
|
|
349
|
-
| ---- | ------------------ | ---------- | ------------ | ------------ | ------------ | ------------ | ------ |
|
|
350
|
-
| 🥇 1 | undici | 13.84K | 14.41 ms | 13.54 ms | 15.39 ms | 26.23 ms | 0 |
|
|
351
|
-
| 🥈 2 | **@hyperttp/core** | **11.95K** | **16.68 ms** | **14.90 ms** | **17.04 ms** | **26.58 ms** | 0 |
|
|
352
|
-
| 3 | bun-fetch | 7.89K | 25.26 ms | 23.04 ms | 33.86 ms | 39.14 ms | 0 |
|
|
353
|
-
| 4 | request | 6.99K | 28.58 ms | 27.19 ms | 34.91 ms | 39.22 ms | 0 |
|
|
354
|
-
| 5 | ky | 5.97K | 33.42 ms | 30.05 ms | 41.58 ms | 64.01 ms | 0 |
|
|
355
|
-
| 6 | axios | 4.71K | 42.39 ms | 40.65 ms | 48.48 ms | 56.20 ms | 0 |
|
|
356
|
-
| 7 | node-fetch | 4.43K | 45.03 ms | 42.18 ms | 53.61 ms | 65.02 ms | 0 |
|
|
357
|
-
| 8 | got | 4.20K | 47.51 ms | 45.20 ms | 55.32 ms | 67.58 ms | 0 |
|
|
358
|
-
| 9 | superagent | 3.18K | 62.66 ms | 61.13 ms | 70.10 ms | 75.46 ms | 0 |
|
|
294
|
+
## Lifecycle
|
|
359
295
|
|
|
360
|
-
|
|
296
|
+
```ts
|
|
297
|
+
const child = core.extend({ verbose: true }); // shares resolved transport leases
|
|
298
|
+
const isolated = core.create({}); // independent transport lifecycle
|
|
361
299
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
| 🥉 3 | request | 7.34K | 27.14 ms | 26.27 ms | 30.96 ms | 34.91 ms | 0 |
|
|
367
|
-
| 4 | **@hyperttp/core** | **7.23K** | **27.58 ms** | **25.68 ms** | **33.76 ms** | **42.75 ms** | 0 |
|
|
368
|
-
| 5 | ky | 6.41K | 31.14 ms | 28.43 ms | 37.55 ms | 61.30 ms | 0 |
|
|
369
|
-
| 6 | axios | 4.87K | 40.96 ms | 39.70 ms | 45.47 ms | 56.43 ms | 0 |
|
|
370
|
-
| 7 | node-fetch | 4.62K | 43.15 ms | 41.12 ms | 49.25 ms | 64.28 ms | 0 |
|
|
371
|
-
| 8 | got | 4.58K | 43.63 ms | 41.57 ms | 48.48 ms | 65.02 ms | 0 |
|
|
372
|
-
| 9 | superagent | 3.32K | 60.11 ms | 59.08 ms | 66.02 ms | 71.26 ms | 0 |
|
|
300
|
+
await child.destroy();
|
|
301
|
+
await isolated.destroy();
|
|
302
|
+
await core.destroy();
|
|
303
|
+
```
|
|
373
304
|
|
|
374
|
-
|
|
305
|
+
- `extend()` creates a related core and shares existing transport ownership.
|
|
306
|
+
- `create()` creates an independent core and does not inherit `customTransport` by default.
|
|
307
|
+
- `destroy()` is idempotent, closes tracked servers and releases transports.
|
|
308
|
+
- `destroy(false)` requests forced shutdown where the transport supports it.
|
|
375
309
|
|
|
376
|
-
|
|
310
|
+
## Error handling
|
|
377
311
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
- **Zero Error Rate**: 0% errors across all scenarios in all benchmarks
|
|
381
|
-
- **Memory Efficiency**: @hyperttp/core uses ~148-184MB in Bun, less than axios (201MB) and got (165-170MB)
|
|
312
|
+
```ts
|
|
313
|
+
import { HyperClientError, TimeoutError } from "@hyperttp/core";
|
|
382
314
|
|
|
383
|
-
|
|
315
|
+
try {
|
|
316
|
+
await core.rest.get("/slow", { timeout: 1_000 });
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (TimeoutError.isTimeoutError(error)) {
|
|
319
|
+
console.error("Request timed out");
|
|
320
|
+
} else if (HyperClientError.isHyperClientError(error)) {
|
|
321
|
+
console.error(error.code, error.message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
384
325
|
|
|
385
|
-
##
|
|
326
|
+
## Scope
|
|
386
327
|
|
|
387
|
-
|
|
328
|
+
The stable core is intentionally limited to:
|
|
388
329
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
330
|
+
1. protocol registration and dispatch;
|
|
331
|
+
2. transport resolution, sharing and shutdown;
|
|
332
|
+
3. client and server lifecycle orchestration;
|
|
333
|
+
4. plugin execution;
|
|
334
|
+
5. the baseline REST protocol and universal error contracts.
|
|
392
335
|
|
|
393
|
-
|
|
394
|
-
|
|
336
|
+
Feature growth should happen through plugins, protocol packages and transport packages. Keeping this
|
|
337
|
+
boundary small makes runtime behavior predictable and allows the core API to stabilize independently
|
|
338
|
+
from the ecosystem around it.
|
|
395
339
|
|
|
396
|
-
|
|
397
|
-
bun run lint
|
|
340
|
+
## Runtime support
|
|
398
341
|
|
|
399
|
-
|
|
400
|
-
|
|
342
|
+
- Node.js 20 and newer;
|
|
343
|
+
- current stable Bun;
|
|
344
|
+
- current stable Deno;
|
|
345
|
+
- modern browsers and edge runtimes with Fetch and Web Streams.
|
|
401
346
|
|
|
402
|
-
|
|
403
|
-
bun run build
|
|
347
|
+
## Development
|
|
404
348
|
|
|
405
|
-
|
|
406
|
-
|
|
349
|
+
```bash
|
|
350
|
+
npm install
|
|
351
|
+
npm run format:check
|
|
352
|
+
npm run lint
|
|
353
|
+
npm run typecheck
|
|
354
|
+
npm run test
|
|
355
|
+
npm run build
|
|
407
356
|
```
|
|
408
357
|
|
|
409
|
-
|
|
358
|
+
The required order is `lint → typecheck → test → build`. CI also verifies browser bundling, runtime
|
|
359
|
+
smoke tests and installation of the generated npm tarball.
|
|
410
360
|
|
|
411
|
-
##
|
|
361
|
+
## License
|
|
412
362
|
|
|
413
363
|
MIT
|
|
414
|
-
|
|
415
|
-
---
|
|
416
|
-
|
|
417
|
-
<p align="center">
|
|
418
|
-
<a href="https://github.com/dirold2">dirold2</a>
|
|
419
|
-
</p>
|