qhttpx 1.9.4 → 2.0.1
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/CHANGELOG.md +21 -0
- package/README.md +28 -35
- package/dist/examples/api-server.js +38 -35
- package/dist/examples/basic.js +3 -4
- package/dist/examples/compression.js +6 -8
- package/dist/examples/cors.js +5 -6
- package/dist/examples/errors.js +12 -11
- package/dist/examples/file-upload.js +4 -6
- package/dist/examples/fusion.js +6 -6
- package/dist/examples/rate-limiting.js +10 -10
- package/dist/examples/validation.js +5 -6
- package/dist/examples/websockets.js +3 -4
- package/dist/package.json +3 -8
- package/dist/src/benchmarks/quantam-users.js +2 -2
- package/dist/src/benchmarks/quick-bench.js +57 -0
- package/dist/src/benchmarks/simple-json.js +133 -22
- package/dist/src/benchmarks/ultra-mode.js +8 -38
- package/dist/src/core/context-pool.d.ts +12 -0
- package/dist/src/core/context-pool.js +34 -0
- package/dist/src/core/fusion.js +0 -2
- package/dist/src/core/metrics.d.ts +3 -1
- package/dist/src/core/metrics.js +11 -5
- package/dist/src/core/scheduler.d.ts +4 -0
- package/dist/src/core/scheduler.js +75 -34
- package/dist/src/core/scope.d.ts +23 -8
- package/dist/src/core/scope.js +53 -14
- package/dist/src/core/serializer.d.ts +1 -1
- package/dist/src/core/serializer.js +45 -7
- package/dist/src/core/server.d.ts +51 -10
- package/dist/src/core/server.js +688 -259
- package/dist/src/core/timer.d.ts +11 -0
- package/dist/src/core/timer.js +29 -0
- package/dist/src/core/types.d.ts +41 -13
- package/dist/src/core/types.js +6 -6
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +19 -20
- package/dist/src/middleware/security.js +6 -1
- package/dist/src/router/radix-tree.d.ts +5 -2
- package/dist/src/router/radix-tree.js +58 -14
- package/dist/src/router/router.d.ts +5 -2
- package/dist/src/router/router.js +80 -63
- package/dist/tests/fusion.test.js +4 -4
- package/dist/tests/rate-limit.test.js +2 -2
- package/dist/tests/schema-routes.test.js +3 -1
- package/docs/AEGIS.md +18 -28
- package/docs/BENCHMARKS.md +8 -6
- package/docs/DATABASE.md +4 -4
- package/docs/MIDDLEWARE.md +3 -3
- package/docs/ROUTING.md +21 -13
- package/docs/VALIDATION.md +9 -31
- package/package.json +3 -8
- package/binding.gyp +0 -18
- package/dist/src/benchmarks/compare-frameworks.js +0 -119
- package/dist/src/benchmarks/compare.js +0 -288
- package/dist/src/buffer-pool.js +0 -70
- package/dist/src/config.js +0 -50
- package/dist/src/cookies.js +0 -59
- package/dist/src/core/native-adapter.d.ts +0 -11
- package/dist/src/core/native-adapter.js +0 -211
- package/dist/src/cors.js +0 -66
- package/dist/src/logger.js +0 -45
- package/dist/src/metrics.js +0 -111
- package/dist/src/native/index.d.ts +0 -32
- package/dist/src/native/index.js +0 -141
- package/dist/src/presets.js +0 -33
- package/dist/src/radix-router.js +0 -89
- package/dist/src/radix-tree.js +0 -81
- package/dist/src/resources.js +0 -25
- package/dist/src/router.js +0 -138
- package/dist/src/scheduler.js +0 -85
- package/dist/src/security.js +0 -69
- package/dist/src/server.js +0 -685
- package/dist/src/signals.js +0 -31
- package/dist/src/static.js +0 -107
- package/dist/src/stream.js +0 -71
- package/dist/src/tasks.js +0 -87
- package/dist/src/testing.js +0 -40
- package/dist/src/types.js +0 -19
- package/dist/src/utils/testing.js +0 -40
- package/dist/src/worker-queue.js +0 -73
- package/dist/tests/native-adapter.test.d.ts +0 -1
- package/dist/tests/native-adapter.test.js +0 -71
- package/prebuilds/darwin-arm64/qhttpx.node +0 -0
- package/prebuilds/linux-x64/qhttpx.node +0 -0
- package/prebuilds/win32-x64/qhttpx.node +0 -0
- package/scripts/install-native.js +0 -26
- package/src/native/README.md +0 -31
- package/src/native/addon.cc +0 -8
- package/src/native/index.ts +0 -158
- package/src/native/picohttpparser.c +0 -608
- package/src/native/picohttpparser.h +0 -76
- package/src/native/server.cc +0 -264
- package/src/native/server.h +0 -30
- /package/dist/src/benchmarks/{compare.d.ts → quick-bench.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.0.0] - 2026-01-21
|
|
6
|
+
**"The Ultra-Simple API Update"**
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Ultra-Simple API**: Introduced `import { app } from "qhttpx"` singleton pattern for zero-boilerplate setup.
|
|
10
|
+
- **Fluent Configuration**: Chained methods `.database()`, `.security()`, `.log()`, `.validate()`, `.production()` for one-line feature enabling.
|
|
11
|
+
- **Return-Based Responses**: Route handlers can now directly return objects (auto-serialized to JSON) or strings, replacing manual `ctx.json()` calls.
|
|
12
|
+
- **Inline Validation**: Declarative schema validation directly in route definitions: `app.post("/", { body: Schema, handler: ... })`.
|
|
13
|
+
- **Semantic Errors**: Added `api.httpError(404, "Message")` helper for consistent, throw-able error handling.
|
|
14
|
+
- **Fluent Rate Limiting**: New `.rateLimit("strict")` presets and `.allow(N).per("interval")` builder for intuitive DDoS protection.
|
|
15
|
+
- **Modular Plugins**: New `plugin(api => ...)` helper for creating reusable, encapsulated route modules.
|
|
16
|
+
- **Production Defaults**: `.production()` method automatically enables Clustering, Compression, Caching, and Adaptive Scheduler.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Architecture**: Complete shift to **Pure TypeScript** core. Removed Native C++ bindings to improve portability, reduce build complexity, and eliminate `node-gyp` dependencies while maintaining high performance.
|
|
20
|
+
- **Routing**: `app.routes()` now accepts plugin modules for cleaner code organization.
|
|
21
|
+
- **Status Codes**: POST requests returning data automatically default to `201 Created`.
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
- **Native Mode**: Removed C++ addon (`node-addon-api`) and associated build steps. QHTTPX is now 100% JS/TS, running everywhere Node.js runs.
|
|
25
|
+
|
|
5
26
|
## [1.9.4] - 2026-01-20
|
|
6
27
|
**"The Silent Performance Update"**
|
|
7
28
|
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
[](https://www.npmjs.com/package/qhttpx)
|
|
15
15
|
[](https://www.npmjs.com/package/qhttpx)
|
|
16
|
+
[](https://qhttpx.gridrr.com)
|
|
16
17
|
[](https://github.com/Quantam-Open-Source/qhttpx/blob/main/LICENSE)
|
|
17
18
|
[](http://makeapullrequest.com)
|
|
18
19
|
[](https://www.typescriptlang.org)
|
|
@@ -44,10 +45,10 @@ Most Node.js frameworks rely on the event loop blindly. QHTTPX introduces a **Co
|
|
|
44
45
|
|
|
45
46
|
| Feature | **QHTTPX** | Fastify | Express | Hono |
|
|
46
47
|
| :--- | :---: | :---: | :---: | :---: |
|
|
47
|
-
| **Core Runtime** | **⚡
|
|
48
|
+
| **Core Runtime** | **⚡ Pure TypeScript** | JS | JS | JS |
|
|
48
49
|
| **Request Fusion** | **✅ Native** | ❌ | ❌ | ❌ |
|
|
49
50
|
| **DDoS Protection** | **✅ Built-in (Aegis)** | ❌ | ❌ | ❌ |
|
|
50
|
-
| **JSON Serialization** | **✅
|
|
51
|
+
| **JSON Serialization** | **✅ Optimized Fast-Path** | Schema-based | Slow | Slow |
|
|
51
52
|
| **Concurrency Model** | **✅ Async Scheduler** | Event Loop | Event Loop | Event Loop |
|
|
52
53
|
| **Rate Limiting** | **✅ Zero-Overhead** | Plugin | Middleware | Middleware |
|
|
53
54
|
| **Routing Algorithm** | **✅ Optimized Radix** | Radix Tree | Linear/Regex | RegExp/Trie |
|
|
@@ -93,13 +94,13 @@ fastify.listen({ port: 3000 });
|
|
|
93
94
|
```typescript
|
|
94
95
|
import { app } from 'qhttpx';
|
|
95
96
|
|
|
96
|
-
app.get('/hello', ({ query, setHeader
|
|
97
|
+
app.get('/hello', ({ query, setHeader }) => {
|
|
97
98
|
const name = query.name || 'World';
|
|
98
99
|
setHeader('X-Powered-By', 'QHTTPX');
|
|
99
|
-
|
|
100
|
+
return { message: `Hello ${name}` };
|
|
100
101
|
});
|
|
101
102
|
|
|
102
|
-
app.
|
|
103
|
+
app.start(3000);
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
---
|
|
@@ -158,8 +159,14 @@ npm install qhttpx tsx
|
|
|
158
159
|
```typescript
|
|
159
160
|
import { app } from 'qhttpx';
|
|
160
161
|
|
|
161
|
-
app.get('/', (
|
|
162
|
-
|
|
162
|
+
app.get('/', () => ({ msg: 'Works!' }));
|
|
163
|
+
|
|
164
|
+
// Ultra-Simple One-Liners
|
|
165
|
+
app.security()
|
|
166
|
+
.log()
|
|
167
|
+
.production();
|
|
168
|
+
|
|
169
|
+
app.start(3000).then(({ port }) => console.log(`Server running on http://localhost:${port}`));
|
|
163
170
|
```
|
|
164
171
|
|
|
165
172
|
Run with: `npx tsx src/index.ts`
|
|
@@ -175,32 +182,32 @@ npm install qhttpx
|
|
|
175
182
|
```javascript
|
|
176
183
|
const { app } = require('qhttpx');
|
|
177
184
|
|
|
178
|
-
app.get('/', (
|
|
179
|
-
app.
|
|
185
|
+
app.get('/', () => ({ msg: 'Works!' }));
|
|
186
|
+
app.start(3000).then(() => console.log('Running on http://localhost:3000'));
|
|
180
187
|
```
|
|
181
188
|
|
|
182
189
|
Run with: `node index.js`
|
|
183
190
|
|
|
184
|
-
### 3. The
|
|
185
|
-
|
|
191
|
+
### 3. The Fluent Way (Advanced Configuration)
|
|
192
|
+
Use the chainable API to configure advanced features like Request Fusion, Metrics, and Body Limits.
|
|
186
193
|
|
|
187
194
|
```typescript
|
|
188
|
-
import {
|
|
195
|
+
import { app } from 'qhttpx';
|
|
189
196
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
});
|
|
197
|
+
app.fusion(true) // Enable Request Coalescing
|
|
198
|
+
.metrics(true) // Enable Prometheus-style metrics
|
|
199
|
+
.bodyLimit(1024 * 1024)// Set 1MB body limit
|
|
200
|
+
.security({ cors: true });
|
|
194
201
|
|
|
195
|
-
app.get(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
json({ page });
|
|
202
|
+
app.get('/heavy-compute', async () => {
|
|
203
|
+
// If 1000 users hit this at once, it executes ONLY ONCE
|
|
204
|
+
return await db.query('SELECT * FROM heavy_table');
|
|
199
205
|
});
|
|
200
206
|
|
|
201
|
-
app.
|
|
207
|
+
app.start(3000);
|
|
202
208
|
```
|
|
203
209
|
|
|
210
|
+
|
|
204
211
|
---
|
|
205
212
|
|
|
206
213
|
## 📚 Documentation
|
|
@@ -218,21 +225,7 @@ Detailed guides for every feature of QHTTPX:
|
|
|
218
225
|
|
|
219
226
|
---
|
|
220
227
|
|
|
221
|
-
## 📊 Benchmarks
|
|
222
|
-
|
|
223
|
-
Benchmarks comparing **QHTTPX v1.8.6** against popular Node.js frameworks.
|
|
224
|
-
*(Run on local environment: Windows, 100 connections, 10 pipelining, 40s duration. Average of 2 runs, taking the second result.)*
|
|
225
228
|
|
|
226
|
-
| Framework | Req/Sec | Latency (Avg) | Throughput |
|
|
227
|
-
| :--- | :--- | :--- | :--- |
|
|
228
|
-
| **Fastify** | 16,050 | 256.78 ms | 3.00 Mb/s |
|
|
229
|
-
| **QHTTPX** | **14,179** | **291.13 ms** | **3.02 Mb/s** |
|
|
230
|
-
| **Hono** | 14,176 | 294.09 ms | 2.43 Mb/s |
|
|
231
|
-
| **Express** | 10,450 | 407.12 ms | 1.94 Mb/s |
|
|
232
|
-
|
|
233
|
-
> **Note**: QHTTPX achieves comparable throughput to Fastify while offering advanced features like **Request Fusion** (deduplication) which can significantly reduce database load in real-world scenarios.
|
|
234
|
-
|
|
235
|
-
---
|
|
236
229
|
|
|
237
230
|
## 🤝 Ecosystem Compatibility
|
|
238
231
|
|
|
@@ -4,21 +4,26 @@ const index_1 = require("../src/index");
|
|
|
4
4
|
const dotenv_1 = require("dotenv");
|
|
5
5
|
// Load .env
|
|
6
6
|
(0, dotenv_1.config)();
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// Configure App via Fluent API
|
|
8
|
+
index_1.app.fusion(true)
|
|
9
|
+
.metrics(true)
|
|
10
|
+
.production() // Enables Compression & Performance Mode
|
|
11
|
+
.security({
|
|
12
|
+
cors: true,
|
|
11
13
|
rateLimit: {
|
|
12
14
|
windowMs: 15 * 60 * 1000,
|
|
13
15
|
max: 100,
|
|
14
16
|
trustProxy: true
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
// Routes
|
|
20
|
+
index_1.app.get('/', ({ json }) => json({ status: 'online', fusion: true }));
|
|
21
|
+
// Fused Endpoint: 1000 concurrent requests -> 1 DB execution
|
|
22
|
+
index_1.app.get('/heavy', async ({ json }) => {
|
|
23
|
+
await new Promise(r => setTimeout(r, 100)); // Simulate DB
|
|
24
|
+
json({ data: 'Expensive Result', timestamp: Date.now() });
|
|
18
25
|
});
|
|
19
|
-
//
|
|
20
|
-
// No manual app.use(rateLimit(...)) needed!
|
|
21
|
-
// 3. Validation Schema
|
|
26
|
+
// Validation Schema
|
|
22
27
|
const UserSchema = {
|
|
23
28
|
body: {
|
|
24
29
|
type: 'object',
|
|
@@ -26,52 +31,50 @@ const UserSchema = {
|
|
|
26
31
|
properties: { name: { type: 'string' }, role: { type: 'string' } }
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
|
-
//
|
|
30
|
-
app.
|
|
31
|
-
// ⚡ Fused Endpoint: 1000 concurrent requests -> 1 DB execution
|
|
32
|
-
app.get('/heavy', async ({ json }) => {
|
|
33
|
-
await new Promise(r => setTimeout(r, 100)); // Simulate DB
|
|
34
|
-
json({ data: 'Expensive Result', timestamp: Date.now() });
|
|
35
|
-
});
|
|
36
|
-
// 🛡️ Validated & Typed Route
|
|
37
|
-
app.post('/users', { schema: UserSchema }, async ({ body, json }) => {
|
|
34
|
+
// Validated & Typed Route
|
|
35
|
+
index_1.app.post('/users', { schema: UserSchema }, async ({ body, json }) => {
|
|
38
36
|
// Body is already validated and typed here
|
|
39
37
|
json({ created: true, user: body }, 201);
|
|
40
38
|
});
|
|
41
|
-
//
|
|
42
|
-
app.post('/auth/verify', async ({ headers, json }) => {
|
|
39
|
+
// Header Auth Example
|
|
40
|
+
index_1.app.post('/auth/verify', async ({ headers, json }) => {
|
|
43
41
|
const token = headers.authorization?.replace('Bearer ', '');
|
|
44
42
|
if (!token)
|
|
45
43
|
return json({ error: 'No token provided' }, 401);
|
|
46
44
|
// Verify token...
|
|
47
45
|
json({ valid: true, token });
|
|
48
46
|
});
|
|
49
|
-
//
|
|
50
|
-
app.post('/upload', ({ files, json }) => {
|
|
47
|
+
// File Uploads
|
|
48
|
+
index_1.app.post('/upload', ({ files, json }) => {
|
|
51
49
|
if (!files?.doc)
|
|
52
50
|
return json({ error: 'No file' }, 400);
|
|
53
51
|
const doc = Array.isArray(files.doc) ? files.doc[0] : files.doc;
|
|
54
52
|
json({ filename: doc.filename, size: doc.size });
|
|
55
53
|
});
|
|
56
|
-
//
|
|
57
|
-
app.upgrade('/chat', (ws) => {
|
|
54
|
+
// WebSockets
|
|
55
|
+
index_1.app.upgrade('/chat', (ws) => {
|
|
58
56
|
ws.join('general'); // Auto-join room
|
|
59
57
|
ws.on('message', (msg) => {
|
|
60
58
|
// Broadcast to room
|
|
61
|
-
app.websocket.to('general').emit(`Echo: ${msg}`);
|
|
59
|
+
index_1.app.websocket.to('general').emit(`Echo: ${msg}`);
|
|
62
60
|
});
|
|
63
61
|
});
|
|
64
|
-
//
|
|
65
|
-
app.onError(({ error, json }) => {
|
|
66
|
-
|
|
62
|
+
// Unified Error Handling
|
|
63
|
+
index_1.app.onError(({ error, json }) => {
|
|
64
|
+
// Use duck typing or check prototype if HttpError is not imported
|
|
65
|
+
// But here we can use the error code or status property
|
|
66
|
+
// Or since we removed HttpError import, we can just check properties
|
|
67
|
+
if (error && typeof error === 'object' && 'status' in error && 'code' in error) {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
const err = error;
|
|
67
70
|
return json({
|
|
68
|
-
error:
|
|
69
|
-
code:
|
|
70
|
-
details:
|
|
71
|
-
},
|
|
71
|
+
error: err.message,
|
|
72
|
+
code: err.code,
|
|
73
|
+
details: err.details
|
|
74
|
+
}, err.status);
|
|
72
75
|
}
|
|
73
76
|
console.error(error); // Log internal error
|
|
74
77
|
json({ error: 'Internal Server Error', handled: true }, 500);
|
|
75
78
|
});
|
|
76
|
-
//
|
|
77
|
-
app.
|
|
79
|
+
// Start Server
|
|
80
|
+
index_1.app.start(3000).then(() => console.log('🚀 Server running on http://localhost:3000'));
|
package/dist/examples/basic.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
app.get('/', ({ json }) => {
|
|
4
|
+
index_1.app.get('/', ({ json }) => {
|
|
6
5
|
json({ message: 'Hello from QHTTPX!' });
|
|
7
6
|
});
|
|
8
|
-
app.
|
|
9
|
-
console.log(
|
|
7
|
+
index_1.app.start(3000).then(({ port }) => {
|
|
8
|
+
console.log(`Server running on http://localhost:${port}`);
|
|
10
9
|
});
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
});
|
|
10
|
-
app.get('/large', ({ json }) => {
|
|
4
|
+
// Enable Production Mode
|
|
5
|
+
// - Enables Compression (Gzip/Brotli)
|
|
6
|
+
// - Sets Performance Mode
|
|
7
|
+
index_1.app.production();
|
|
8
|
+
index_1.app.get('/large', ({ json }) => {
|
|
11
9
|
// Generate a large response to trigger compression
|
|
12
10
|
const data = Array(1000).fill('some repeated data to compress');
|
|
13
11
|
json({ data });
|
|
14
12
|
});
|
|
15
|
-
app.
|
|
13
|
+
index_1.app.start(3000).then(() => {
|
|
16
14
|
console.log('Compression-enabled server running on http://localhost:3000');
|
|
17
15
|
});
|
package/dist/examples/cors.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// cors: true
|
|
7
|
-
// Advanced: Configure specific origins
|
|
4
|
+
// Enable CORS via Security Helper
|
|
5
|
+
index_1.app.security({
|
|
8
6
|
cors: {
|
|
9
7
|
origin: ['http://localhost:5173', 'https://myapp.com'],
|
|
10
8
|
methods: ['GET', 'POST'],
|
|
11
9
|
credentials: true
|
|
12
10
|
}
|
|
13
11
|
});
|
|
14
|
-
app.
|
|
12
|
+
// Or simple: app.security({ cors: true });
|
|
13
|
+
index_1.app.get('/api/data', ({ json }) => {
|
|
15
14
|
json({ data: 'This data is accessible via CORS' });
|
|
16
15
|
});
|
|
17
|
-
app.
|
|
16
|
+
index_1.app.start(3000).then(() => {
|
|
18
17
|
console.log('CORS-enabled server running on http://localhost:3000');
|
|
19
18
|
});
|
package/dist/examples/errors.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
app.
|
|
6
|
-
throw new index_1.NotFoundException('Resource not found');
|
|
4
|
+
index_1.app.get('/missing', () => {
|
|
5
|
+
throw index_1.app.error(404, 'Resource not found');
|
|
7
6
|
});
|
|
8
|
-
app.get('/private', () => {
|
|
9
|
-
throw
|
|
7
|
+
index_1.app.get('/private', () => {
|
|
8
|
+
throw index_1.app.error(403, 'You do not have access');
|
|
10
9
|
});
|
|
11
10
|
// Custom global error handler
|
|
12
|
-
app.onError(({ error, json }) => {
|
|
13
|
-
if (error
|
|
11
|
+
index_1.app.onError(({ error, json }) => {
|
|
12
|
+
if (error && typeof error === 'object' && 'status' in error) {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
const err = error;
|
|
14
15
|
return json({
|
|
15
16
|
status: 'error',
|
|
16
|
-
code:
|
|
17
|
-
message:
|
|
18
|
-
},
|
|
17
|
+
code: err.code,
|
|
18
|
+
message: err.message
|
|
19
|
+
}, err.status);
|
|
19
20
|
}
|
|
20
21
|
console.error('Unexpected error:', error);
|
|
21
22
|
json({ status: 'error', message: 'Internal Server Error' }, 500);
|
|
22
23
|
});
|
|
23
|
-
app.
|
|
24
|
+
index_1.app.start(3000).then(() => {
|
|
24
25
|
console.log('Error handling demo running on http://localhost:3000');
|
|
25
26
|
});
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
});
|
|
8
|
-
app.post('/upload', ({ files, json }) => {
|
|
4
|
+
// Configure upload limits
|
|
5
|
+
index_1.app.bodyLimit(10 * 1024 * 1024); // 10MB
|
|
6
|
+
index_1.app.post('/upload', ({ files, json }) => {
|
|
9
7
|
if (!files || !files.document) {
|
|
10
8
|
return json({ error: 'No file uploaded' }, 400);
|
|
11
9
|
}
|
|
@@ -18,7 +16,7 @@ app.post('/upload', ({ files, json }) => {
|
|
|
18
16
|
size: doc.size
|
|
19
17
|
});
|
|
20
18
|
});
|
|
21
|
-
app.
|
|
19
|
+
index_1.app.start(3000).then(() => {
|
|
22
20
|
console.log('Upload server running on http://localhost:3000');
|
|
23
21
|
console.log('Send POST to /upload with form-data field "document"');
|
|
24
22
|
});
|
package/dist/examples/fusion.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// Enable Request Fusion (Request Coalescing)
|
|
5
|
+
index_1.app.fusion(true);
|
|
6
|
+
// Enable Production Mode (Compression + Opts)
|
|
7
|
+
index_1.app.production();
|
|
8
8
|
// Simulate a slow database call
|
|
9
9
|
const heavyTask = async () => {
|
|
10
10
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
@@ -12,10 +12,10 @@ const heavyTask = async () => {
|
|
|
12
12
|
};
|
|
13
13
|
// If 1000 users hit this endpoint simultaneously,
|
|
14
14
|
// the handler runs ONLY ONCE, and the result is shared.
|
|
15
|
-
app.get('/heavy', async ({ json }) => {
|
|
15
|
+
index_1.app.get('/heavy', async ({ json }) => {
|
|
16
16
|
const result = await heavyTask();
|
|
17
17
|
json(result);
|
|
18
18
|
});
|
|
19
|
-
app.
|
|
19
|
+
index_1.app.start(3000).then(() => {
|
|
20
20
|
console.log('Fusion-enabled server running on http://localhost:3000');
|
|
21
21
|
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
app.get('/', ({ json }) => {
|
|
4
|
+
// 1. Fluent Rate Limiting
|
|
5
|
+
// Limit to 100 requests per 15 minutes
|
|
6
|
+
index_1.app.rateLimit(100, '15m');
|
|
7
|
+
// Alternative: Use presets
|
|
8
|
+
// app.rateLimit('strict'); // 10 req / 15 min
|
|
9
|
+
// app.rateLimit('relaxed'); // 1000 req / 1 hour
|
|
10
|
+
// Alternative: Fluent Builder
|
|
11
|
+
// app.allow(50).per('1m');
|
|
12
|
+
index_1.app.get('/', ({ json }) => {
|
|
13
13
|
json({ status: 'OK', message: 'Request accepted' });
|
|
14
14
|
});
|
|
15
|
-
app.
|
|
15
|
+
index_1.app.start(3000).then(() => {
|
|
16
16
|
console.log('Rate-limited server running on http://localhost:3000');
|
|
17
17
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Initialize Validator
|
|
5
|
+
index_1.app.validate();
|
|
6
6
|
const UserSchema = {
|
|
7
7
|
body: {
|
|
8
8
|
type: 'object',
|
|
@@ -13,11 +13,10 @@ const UserSchema = {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// 'body' is already validated and typed here (if using TS with inferred types)
|
|
16
|
+
index_1.app.post('/register', { schema: UserSchema }, ({ body, json }) => {
|
|
17
|
+
// 'body' is already validated and typed here
|
|
19
18
|
json({ success: true, user: body });
|
|
20
19
|
});
|
|
21
|
-
app.
|
|
20
|
+
index_1.app.start(3000).then(() => {
|
|
22
21
|
console.log('Validation server running on http://localhost:3000');
|
|
23
22
|
});
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../src/index");
|
|
4
|
-
|
|
5
|
-
app.upgrade('/chat', (ws) => {
|
|
4
|
+
index_1.app.upgrade('/chat', (ws) => {
|
|
6
5
|
console.log('Client connected');
|
|
7
6
|
ws.join('general');
|
|
8
7
|
ws.on('message', (msg) => {
|
|
9
8
|
console.log(`Received: ${msg}`);
|
|
10
9
|
// Broadcast to everyone in 'general' room
|
|
11
|
-
app.websocket.to('general').emit(`Echo: ${msg}`);
|
|
10
|
+
index_1.app.websocket.to('general').emit(`Echo: ${msg}`);
|
|
12
11
|
});
|
|
13
12
|
ws.on('close', () => {
|
|
14
13
|
console.log('Client disconnected');
|
|
15
14
|
});
|
|
16
15
|
});
|
|
17
|
-
app.
|
|
16
|
+
index_1.app.start(3000).then(() => {
|
|
18
17
|
console.log('WebSocket Server running on http://localhost:3000');
|
|
19
18
|
console.log('Test with a WebSocket client at ws://localhost:3000/chat');
|
|
20
19
|
});
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qhttpx",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"gypfile": false,
|
|
5
5
|
"description": "The Ultra-Fast HTTP Framework for Node.js",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -17,10 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
-
"src/native",
|
|
21
|
-
"prebuilds",
|
|
22
20
|
"scripts",
|
|
23
|
-
"binding.gyp",
|
|
24
21
|
"README.md",
|
|
25
22
|
"LICENSE",
|
|
26
23
|
"CHANGELOG.md",
|
|
@@ -30,7 +27,6 @@
|
|
|
30
27
|
"doc": "docs"
|
|
31
28
|
},
|
|
32
29
|
"scripts": {
|
|
33
|
-
"prebuild": "node -e \"try{require('child_process').execSync('prebuildify --napi --strip', {stdio: 'ignore'})}catch(e){}\"",
|
|
34
30
|
"prepublishOnly": "npm run build",
|
|
35
31
|
"build": "tsc -p tsconfig.json",
|
|
36
32
|
"lint": "eslint src tests --ext .ts",
|
|
@@ -72,6 +68,7 @@
|
|
|
72
68
|
"type": "git",
|
|
73
69
|
"url": "https://github.com/Quantam-Open-Source/qhttpx"
|
|
74
70
|
},
|
|
71
|
+
"homepage": "https://qhttpx.gridrr.com",
|
|
75
72
|
"type": "commonjs",
|
|
76
73
|
"devDependencies": {
|
|
77
74
|
"@types/autocannon": "^7.12.7",
|
|
@@ -91,7 +88,6 @@
|
|
|
91
88
|
"eslint-plugin-prettier": "^5.5.5",
|
|
92
89
|
"mongodb": "^7.0.0",
|
|
93
90
|
"pg": "^8.17.1",
|
|
94
|
-
"prebuildify": "^6.0.1",
|
|
95
91
|
"prettier": "^3.8.0",
|
|
96
92
|
"tsx": "^4.21.0",
|
|
97
93
|
"typescript": "^5.9.3",
|
|
@@ -101,8 +97,7 @@
|
|
|
101
97
|
"better-sqlite3": "^12.6.2",
|
|
102
98
|
"busboy": "^1.6.0",
|
|
103
99
|
"fast-json-stringify": "^5.15.1",
|
|
104
|
-
"
|
|
105
|
-
"node-gyp-build": "^4.8.4",
|
|
100
|
+
"fastify": "^5.7.1",
|
|
106
101
|
"pino": "^10.2.0",
|
|
107
102
|
"pino-pretty": "^13.1.3",
|
|
108
103
|
"quantam-async": "^0.1.1",
|
|
@@ -7,8 +7,8 @@ async function run() {
|
|
|
7
7
|
maxConcurrency: 512,
|
|
8
8
|
requestTimeoutMs: 10000,
|
|
9
9
|
});
|
|
10
|
-
app.get('/json', (
|
|
11
|
-
|
|
10
|
+
app.get('/json', ({ json }) => {
|
|
11
|
+
json({ message: 'hello from qhttpx' });
|
|
12
12
|
});
|
|
13
13
|
const { port } = await app.listen(0, '127.0.0.1');
|
|
14
14
|
const url = `http://127.0.0.1:${port}/json`;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const autocannon_1 = __importDefault(require("autocannon"));
|
|
7
|
+
const index_1 = require("../index");
|
|
8
|
+
function runAutocannon(url) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const instance = (0, autocannon_1.default)({
|
|
11
|
+
url,
|
|
12
|
+
connections: 100,
|
|
13
|
+
pipelining: 10,
|
|
14
|
+
duration: 5, // Shorter duration for quick verification
|
|
15
|
+
}, (err, result) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
reject(err);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
resolve(result);
|
|
21
|
+
});
|
|
22
|
+
instance.on('error', (err) => {
|
|
23
|
+
reject(err);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function run() {
|
|
28
|
+
const payloadBuffer = Buffer.from(JSON.stringify({ message: 'hello from qhttpx' }));
|
|
29
|
+
const app = new index_1.QHTTPX({
|
|
30
|
+
maxConcurrency: 512,
|
|
31
|
+
metricsEnabled: false,
|
|
32
|
+
jsonSerializer: () => payloadBuffer,
|
|
33
|
+
});
|
|
34
|
+
app.get('/json', ({ json }) => {
|
|
35
|
+
json({ message: 'hello from qhttpx' });
|
|
36
|
+
});
|
|
37
|
+
const { port } = await app.listen(0, '127.0.0.1');
|
|
38
|
+
const url = `http://127.0.0.1:${port}/json`;
|
|
39
|
+
console.log('Running quick benchmark...');
|
|
40
|
+
const result = await runAutocannon(url);
|
|
41
|
+
autocannon_1.default.printResult(result);
|
|
42
|
+
const totalRequests = result.requests.total;
|
|
43
|
+
const sent = result.requests.sent;
|
|
44
|
+
const avgRps = result.requests.average.toFixed(0);
|
|
45
|
+
const p99 = result.latency.p99.toFixed(1);
|
|
46
|
+
const connections = result.connections;
|
|
47
|
+
const pipelining = result.pipelining;
|
|
48
|
+
console.log(`QHTTPX QUICK bench: total=${totalRequests} (sent=${sent}) req, ` +
|
|
49
|
+
`${avgRps} req/sec, p99=${p99}ms, connections=${connections}, ` +
|
|
50
|
+
`pipelining=${pipelining}`);
|
|
51
|
+
await app.close();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
run().catch((err) => {
|
|
55
|
+
console.error(err);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|