qhttpx 1.9.2 → 1.9.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/CHANGELOG.md +8 -0
- package/README.md +79 -17
- package/dist/examples/api-server.d.ts +1 -0
- package/dist/examples/api-server.js +77 -0
- package/dist/examples/basic.d.ts +1 -0
- package/dist/examples/basic.js +10 -0
- package/dist/examples/compression.d.ts +1 -0
- package/dist/examples/compression.js +17 -0
- package/dist/examples/cors.d.ts +1 -0
- package/dist/examples/cors.js +19 -0
- package/dist/examples/errors.d.ts +1 -0
- package/dist/examples/errors.js +25 -0
- package/dist/examples/file-upload.d.ts +1 -0
- package/dist/examples/file-upload.js +24 -0
- package/dist/examples/fusion.d.ts +1 -0
- package/dist/examples/fusion.js +21 -0
- package/dist/examples/rate-limiting.d.ts +1 -0
- package/dist/examples/rate-limiting.js +17 -0
- package/dist/examples/validation.d.ts +1 -0
- package/dist/examples/validation.js +23 -0
- package/dist/examples/websockets.d.ts +1 -0
- package/dist/examples/websockets.js +20 -0
- package/dist/package.json +112 -0
- package/dist/src/benchmarks/compare-frameworks.js +119 -0
- package/dist/src/benchmarks/compare.d.ts +1 -0
- package/dist/src/benchmarks/compare.js +288 -0
- package/dist/src/benchmarks/quantam-users.d.ts +1 -0
- package/dist/src/benchmarks/quantam-users.js +56 -0
- package/dist/src/benchmarks/simple-json.d.ts +1 -0
- package/dist/src/benchmarks/simple-json.js +60 -0
- package/dist/src/benchmarks/ultra-mode.d.ts +1 -0
- package/dist/src/benchmarks/ultra-mode.js +94 -0
- package/dist/src/buffer-pool.js +70 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +222 -0
- package/dist/src/client/index.d.ts +17 -0
- package/dist/src/client/index.js +72 -0
- package/dist/src/config.js +50 -0
- package/dist/src/cookies.js +59 -0
- package/dist/src/core/batch.d.ts +24 -0
- package/dist/src/core/batch.js +97 -0
- package/dist/src/core/body-parser.d.ts +15 -0
- package/dist/src/core/body-parser.js +121 -0
- package/dist/src/core/buffer-pool.d.ts +41 -0
- package/dist/src/core/buffer-pool.js +70 -0
- package/dist/src/core/config.d.ts +7 -0
- package/dist/src/core/config.js +50 -0
- package/dist/src/core/errors.d.ts +34 -0
- package/dist/src/core/errors.js +70 -0
- package/dist/src/core/fusion.d.ts +20 -0
- package/dist/src/core/fusion.js +193 -0
- package/dist/src/core/logger.d.ts +22 -0
- package/dist/src/core/logger.js +49 -0
- package/dist/src/core/metrics.d.ts +48 -0
- package/dist/src/core/metrics.js +117 -0
- package/dist/src/core/native-adapter.d.ts +11 -0
- package/dist/src/core/native-adapter.js +211 -0
- package/dist/src/core/resources.d.ts +9 -0
- package/dist/src/core/resources.js +25 -0
- package/dist/src/core/scheduler.d.ts +34 -0
- package/dist/src/core/scheduler.js +85 -0
- package/dist/src/core/scope.d.ts +26 -0
- package/dist/src/core/scope.js +68 -0
- package/dist/src/core/serializer.d.ts +10 -0
- package/dist/src/core/serializer.js +44 -0
- package/dist/src/core/server.d.ts +138 -0
- package/dist/src/core/server.js +1082 -0
- package/dist/src/core/stream.d.ts +15 -0
- package/dist/src/core/stream.js +71 -0
- package/dist/src/core/tasks.d.ts +29 -0
- package/dist/src/core/tasks.js +87 -0
- package/dist/src/core/types.d.ts +173 -0
- package/dist/src/core/types.js +19 -0
- package/dist/src/core/websocket.d.ts +25 -0
- package/dist/src/core/websocket.js +86 -0
- package/dist/src/core/worker-queue.d.ts +41 -0
- package/dist/src/core/worker-queue.js +73 -0
- package/dist/src/cors.js +66 -0
- package/dist/src/database/adapters/memory.d.ts +21 -0
- package/dist/src/database/adapters/memory.js +90 -0
- package/dist/src/database/adapters/mongo.d.ts +11 -0
- package/dist/src/database/adapters/mongo.js +141 -0
- package/dist/src/database/adapters/postgres.d.ts +10 -0
- package/dist/src/database/adapters/postgres.js +111 -0
- package/dist/src/database/adapters/sqlite.d.ts +10 -0
- package/dist/src/database/adapters/sqlite.js +42 -0
- package/dist/src/database/coalescer.d.ts +14 -0
- package/dist/src/database/coalescer.js +134 -0
- package/dist/src/database/manager.d.ts +35 -0
- package/dist/src/database/manager.js +87 -0
- package/dist/src/database/types.d.ts +20 -0
- package/dist/src/database/types.js +2 -0
- package/dist/src/index.d.ts +50 -0
- package/dist/src/index.js +91 -0
- package/dist/src/logger.js +45 -0
- package/dist/src/metrics.js +111 -0
- package/dist/src/middleware/compression.d.ts +2 -0
- package/dist/src/middleware/compression.js +133 -0
- package/dist/src/middleware/cors.d.ts +2 -0
- package/dist/src/middleware/cors.js +66 -0
- package/dist/src/middleware/presets.d.ts +16 -0
- package/dist/src/middleware/presets.js +52 -0
- package/dist/src/middleware/rate-limit.d.ts +14 -0
- package/dist/src/middleware/rate-limit.js +83 -0
- package/dist/src/middleware/security.d.ts +21 -0
- package/dist/src/middleware/security.js +69 -0
- package/dist/src/middleware/static.d.ts +11 -0
- package/dist/src/middleware/static.js +191 -0
- package/dist/src/native/index.d.ts +32 -0
- package/dist/src/native/index.js +141 -0
- package/dist/src/openapi/generator.d.ts +19 -0
- package/dist/src/openapi/generator.js +149 -0
- package/dist/src/presets.js +33 -0
- package/dist/src/radix-router.js +89 -0
- package/dist/src/radix-tree.js +81 -0
- package/dist/src/resources.js +25 -0
- package/dist/src/router/radix-router.d.ts +18 -0
- package/dist/src/router/radix-router.js +89 -0
- package/dist/src/router/radix-tree.d.ts +18 -0
- package/dist/src/router/radix-tree.js +131 -0
- package/dist/src/router/router.d.ts +34 -0
- package/dist/src/router/router.js +186 -0
- package/dist/src/router.js +138 -0
- package/dist/src/scheduler.js +85 -0
- package/dist/src/security.js +69 -0
- package/dist/src/server.js +685 -0
- package/dist/src/signals.js +31 -0
- package/dist/src/static.js +107 -0
- package/dist/src/stream.js +71 -0
- package/dist/src/tasks.js +87 -0
- package/dist/src/testing/index.d.ts +25 -0
- package/dist/src/testing/index.js +84 -0
- package/dist/src/testing.js +40 -0
- package/dist/src/types.js +19 -0
- package/dist/src/utils/cookies.d.ts +3 -0
- package/dist/src/utils/cookies.js +59 -0
- package/dist/src/utils/logger.d.ts +12 -0
- package/dist/src/utils/logger.js +45 -0
- package/dist/src/utils/signals.d.ts +6 -0
- package/dist/src/utils/signals.js +31 -0
- package/dist/src/utils/sse.d.ts +6 -0
- package/dist/src/utils/sse.js +32 -0
- package/dist/src/utils/testing.js +40 -0
- package/dist/src/validation/index.d.ts +3 -0
- package/dist/src/validation/index.js +19 -0
- package/dist/src/validation/simple.d.ts +5 -0
- package/dist/src/validation/simple.js +102 -0
- package/dist/src/validation/types.d.ts +32 -0
- package/dist/src/validation/types.js +12 -0
- package/dist/src/validation/zod.d.ts +4 -0
- package/dist/src/validation/zod.js +18 -0
- package/dist/src/views/index.d.ts +1 -0
- package/dist/src/views/index.js +17 -0
- package/dist/src/views/types.d.ts +3 -0
- package/dist/src/views/types.js +2 -0
- package/dist/src/worker-queue.js +73 -0
- package/dist/tests/adapters.test.d.ts +1 -0
- package/dist/tests/adapters.test.js +106 -0
- package/dist/tests/batch.test.d.ts +1 -0
- package/dist/tests/batch.test.js +117 -0
- package/dist/tests/body-parser.test.d.ts +1 -0
- package/dist/tests/body-parser.test.js +52 -0
- package/dist/tests/compression-sse.test.d.ts +1 -0
- package/dist/tests/compression-sse.test.js +87 -0
- package/dist/tests/cookies.test.d.ts +1 -0
- package/dist/tests/cookies.test.js +63 -0
- package/dist/tests/cors.test.d.ts +1 -0
- package/dist/tests/cors.test.js +55 -0
- package/dist/tests/database.test.d.ts +1 -0
- package/dist/tests/database.test.js +80 -0
- package/dist/tests/dx.test.d.ts +1 -0
- package/dist/tests/dx.test.js +114 -0
- package/dist/tests/ecosystem.test.d.ts +1 -0
- package/dist/tests/ecosystem.test.js +133 -0
- package/dist/tests/features.test.d.ts +1 -0
- package/dist/tests/features.test.js +47 -0
- package/dist/tests/fusion.test.d.ts +1 -0
- package/dist/tests/fusion.test.js +92 -0
- package/dist/tests/http-basic.test.d.ts +1 -0
- package/dist/tests/http-basic.test.js +124 -0
- package/dist/tests/logger.test.d.ts +1 -0
- package/dist/tests/logger.test.js +33 -0
- package/dist/tests/middleware.test.d.ts +1 -0
- package/dist/tests/middleware.test.js +109 -0
- package/dist/tests/native-adapter.test.d.ts +1 -0
- package/dist/tests/native-adapter.test.js +71 -0
- package/dist/tests/observability.test.d.ts +1 -0
- package/dist/tests/observability.test.js +59 -0
- package/dist/tests/openapi.test.d.ts +1 -0
- package/dist/tests/openapi.test.js +64 -0
- package/dist/tests/plugin.test.d.ts +1 -0
- package/dist/tests/plugin.test.js +65 -0
- package/dist/tests/plugins.test.d.ts +1 -0
- package/dist/tests/plugins.test.js +71 -0
- package/dist/tests/rate-limit.test.d.ts +1 -0
- package/dist/tests/rate-limit.test.js +77 -0
- package/dist/tests/resources.test.d.ts +1 -0
- package/dist/tests/resources.test.js +47 -0
- package/dist/tests/scheduler.test.d.ts +1 -0
- package/dist/tests/scheduler.test.js +46 -0
- package/dist/tests/schema-routes.test.d.ts +1 -0
- package/dist/tests/schema-routes.test.js +77 -0
- package/dist/tests/security.test.d.ts +1 -0
- package/dist/tests/security.test.js +83 -0
- package/dist/tests/server-db.test.d.ts +1 -0
- package/dist/tests/server-db.test.js +72 -0
- package/dist/tests/smoke.test.d.ts +1 -0
- package/dist/tests/smoke.test.js +10 -0
- package/dist/tests/sqlite-fusion.test.d.ts +1 -0
- package/dist/tests/sqlite-fusion.test.js +92 -0
- package/dist/tests/static.test.d.ts +1 -0
- package/dist/tests/static.test.js +102 -0
- package/dist/tests/stream.test.d.ts +1 -0
- package/dist/tests/stream.test.js +44 -0
- package/dist/tests/task-metrics.test.d.ts +1 -0
- package/dist/tests/task-metrics.test.js +53 -0
- package/dist/tests/tasks.test.d.ts +1 -0
- package/dist/tests/tasks.test.js +62 -0
- package/dist/tests/testing.test.d.ts +1 -0
- package/dist/tests/testing.test.js +47 -0
- package/dist/tests/validation.test.d.ts +1 -0
- package/dist/tests/validation.test.js +107 -0
- package/dist/tests/websocket.test.d.ts +1 -0
- package/dist/tests/websocket.test.js +146 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +9 -0
- package/docs/FUSION.md +19 -0
- package/package.json +2 -1
- 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 +26 -0
|
@@ -0,0 +1,288 @@
|
|
|
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
|
+
const fastify_1 = __importDefault(require("fastify"));
|
|
9
|
+
const express_1 = __importDefault(require("express"));
|
|
10
|
+
const hono_1 = require("hono");
|
|
11
|
+
const node_server_1 = require("@hono/node-server");
|
|
12
|
+
const koa_1 = __importDefault(require("koa"));
|
|
13
|
+
const polka_1 = __importDefault(require("polka"));
|
|
14
|
+
const http_1 = __importDefault(require("http"));
|
|
15
|
+
const PORT = 3000;
|
|
16
|
+
const DURATION = 300; // Testing duration
|
|
17
|
+
const CONNECTIONS = 500; // Connections
|
|
18
|
+
const PIPELINING = 20; // Request pipelining
|
|
19
|
+
const results = [];
|
|
20
|
+
function printProgress(name, timeLeft, total) {
|
|
21
|
+
const percent = Math.max(0, Math.min(100, Math.round(((total - timeLeft) / total) * 100)));
|
|
22
|
+
const barLength = 30;
|
|
23
|
+
const filled = Math.round((barLength * percent) / 100);
|
|
24
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barLength - filled);
|
|
25
|
+
// Clear line and move cursor to start
|
|
26
|
+
process.stdout.clearLine(0);
|
|
27
|
+
process.stdout.cursorTo(0);
|
|
28
|
+
process.stdout.write(`${name} [${bar}] ${percent}% | Time Left: ${timeLeft}s`);
|
|
29
|
+
}
|
|
30
|
+
const frameworks = [
|
|
31
|
+
{
|
|
32
|
+
name: 'QHTTPX (Native)',
|
|
33
|
+
control: () => {
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
let app;
|
|
36
|
+
return {
|
|
37
|
+
start: async () => {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
app = (0, index_1.createHttpApp)();
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
app.get('/', ({ json }) => json({ hello: 'world' }));
|
|
42
|
+
app.listen(PORT, () => resolve());
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
stop: async () => {
|
|
46
|
+
await app.close();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Fastify',
|
|
53
|
+
control: () => {
|
|
54
|
+
const app = (0, fastify_1.default)();
|
|
55
|
+
app.get('/', async () => ({ hello: 'world' }));
|
|
56
|
+
return {
|
|
57
|
+
start: async () => {
|
|
58
|
+
await app.listen({ port: PORT });
|
|
59
|
+
},
|
|
60
|
+
stop: async () => {
|
|
61
|
+
await app.close();
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Hono (Node)',
|
|
68
|
+
control: () => {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
let server;
|
|
71
|
+
return {
|
|
72
|
+
start: async () => {
|
|
73
|
+
const app = new hono_1.Hono();
|
|
74
|
+
app.get('/', (c) => c.json({ hello: 'world' }));
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
try {
|
|
77
|
+
server = (0, node_server_1.serve)({ fetch: app.fetch, port: PORT }, () => resolve());
|
|
78
|
+
server.on('error', reject);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
reject(e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
stop: async () => {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
if (server && typeof server.close === 'function') {
|
|
88
|
+
server.removeAllListeners('error');
|
|
89
|
+
server.close(() => resolve());
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
resolve();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'Express',
|
|
101
|
+
control: () => {
|
|
102
|
+
let server;
|
|
103
|
+
return {
|
|
104
|
+
start: async () => {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
const app = (0, express_1.default)();
|
|
107
|
+
app.disable('etag');
|
|
108
|
+
app.disable('x-powered-by');
|
|
109
|
+
app.get('/', (req, res) => res.json({ hello: 'world' }));
|
|
110
|
+
server = app.listen(PORT, () => resolve());
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
stop: async () => {
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
server.close(() => resolve());
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'Koa',
|
|
123
|
+
control: () => {
|
|
124
|
+
let server;
|
|
125
|
+
return {
|
|
126
|
+
start: async () => {
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
const app = new koa_1.default();
|
|
129
|
+
app.use((ctx) => {
|
|
130
|
+
ctx.body = { hello: 'world' };
|
|
131
|
+
});
|
|
132
|
+
server = app.listen(PORT, () => resolve());
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
stop: async () => {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
server.close(() => resolve());
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'Polka',
|
|
145
|
+
control: () => {
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
let server;
|
|
148
|
+
return {
|
|
149
|
+
start: async () => {
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
const app = (0, polka_1.default)();
|
|
152
|
+
app.get('/', (req, res) => {
|
|
153
|
+
res.setHeader('Content-Type', 'application/json');
|
|
154
|
+
res.end(JSON.stringify({ hello: 'world' }));
|
|
155
|
+
});
|
|
156
|
+
server = app.listen(PORT, () => resolve());
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
stop: async () => {
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
server.server.close(() => resolve());
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'Restify',
|
|
169
|
+
control: () => {
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
let server;
|
|
172
|
+
return {
|
|
173
|
+
start: async () => {
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
try {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
177
|
+
const restify = require('restify');
|
|
178
|
+
server = restify.createServer();
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
server.get('/', (req, res, next) => {
|
|
181
|
+
res.send({ hello: 'world' });
|
|
182
|
+
next();
|
|
183
|
+
});
|
|
184
|
+
server.listen(PORT, () => resolve());
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
reject(e);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
stop: async () => {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
if (server)
|
|
194
|
+
server.close(() => resolve());
|
|
195
|
+
else
|
|
196
|
+
resolve();
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Node Native',
|
|
204
|
+
control: () => {
|
|
205
|
+
let server;
|
|
206
|
+
return {
|
|
207
|
+
start: async () => {
|
|
208
|
+
return new Promise((resolve) => {
|
|
209
|
+
server = http_1.default.createServer((req, res) => {
|
|
210
|
+
res.setHeader('Content-Type', 'application/json');
|
|
211
|
+
res.end(JSON.stringify({ hello: 'world' }));
|
|
212
|
+
});
|
|
213
|
+
server.listen(PORT, () => resolve());
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
stop: async () => {
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
server.close(() => resolve());
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
async function run() {
|
|
226
|
+
const origLog = console.log;
|
|
227
|
+
const origError = console.error;
|
|
228
|
+
const origWarn = console.warn;
|
|
229
|
+
origLog(`\nStarting Benchmark Comparison`);
|
|
230
|
+
origLog(`Duration: ${DURATION}s | Connections: ${CONNECTIONS}\n`);
|
|
231
|
+
for (const fw of frameworks) {
|
|
232
|
+
origLog(`Preparing ${fw.name}...`);
|
|
233
|
+
const ctrl = fw.control();
|
|
234
|
+
try {
|
|
235
|
+
await ctrl.start();
|
|
236
|
+
// Wait for server to be fully ready
|
|
237
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
238
|
+
const startTime = Date.now();
|
|
239
|
+
const interval = setInterval(() => {
|
|
240
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
241
|
+
const timeLeft = Math.max(0, Math.round(DURATION - elapsed));
|
|
242
|
+
printProgress(fw.name, timeLeft, DURATION);
|
|
243
|
+
}, 200);
|
|
244
|
+
// Suppress autocannon logs
|
|
245
|
+
console.log = () => { };
|
|
246
|
+
console.error = () => { };
|
|
247
|
+
console.warn = () => { };
|
|
248
|
+
const result = await (0, autocannon_1.default)({
|
|
249
|
+
url: `http://localhost:${PORT}`,
|
|
250
|
+
connections: CONNECTIONS,
|
|
251
|
+
pipelining: PIPELINING,
|
|
252
|
+
duration: DURATION,
|
|
253
|
+
});
|
|
254
|
+
console.log = origLog;
|
|
255
|
+
console.error = origError;
|
|
256
|
+
console.warn = origWarn;
|
|
257
|
+
clearInterval(interval);
|
|
258
|
+
printProgress(fw.name, 0, DURATION);
|
|
259
|
+
origLog('\n');
|
|
260
|
+
results.push({
|
|
261
|
+
name: fw.name,
|
|
262
|
+
reqs: result.requests.average,
|
|
263
|
+
latency: result.latency.average,
|
|
264
|
+
p50: result.latency.p50,
|
|
265
|
+
p90: result.latency.p90,
|
|
266
|
+
p99: result.latency.p99,
|
|
267
|
+
max: result.latency.max,
|
|
268
|
+
throughput: (result.throughput.average / 1024 / 1024).toFixed(2),
|
|
269
|
+
});
|
|
270
|
+
await ctrl.stop();
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
console.log = origLog;
|
|
274
|
+
console.error = origError;
|
|
275
|
+
console.warn = origWarn;
|
|
276
|
+
origError(`Error benchmarking ${fw.name}:`, err);
|
|
277
|
+
try {
|
|
278
|
+
await ctrl.stop();
|
|
279
|
+
}
|
|
280
|
+
catch { }
|
|
281
|
+
}
|
|
282
|
+
// Cool down - allow time for port to be released
|
|
283
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
284
|
+
}
|
|
285
|
+
origLog('\n--- Final Results ---');
|
|
286
|
+
console.table(results.sort((a, b) => b.reqs - a.reqs));
|
|
287
|
+
}
|
|
288
|
+
run().catch(console.error);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const quantam_async_1 = require("quantam-async");
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
async function run() {
|
|
6
|
+
const app = new index_1.QHTTPX({
|
|
7
|
+
maxConcurrency: 512,
|
|
8
|
+
requestTimeoutMs: 10000,
|
|
9
|
+
});
|
|
10
|
+
app.get('/json', (ctx) => {
|
|
11
|
+
ctx.json({ message: 'hello from qhttpx' });
|
|
12
|
+
});
|
|
13
|
+
const { port } = await app.listen(0, '127.0.0.1');
|
|
14
|
+
const url = `http://127.0.0.1:${port}/json`;
|
|
15
|
+
const requestsPerUser = 10;
|
|
16
|
+
const userCount = 10000;
|
|
17
|
+
const maxConcurrentUsers = 1000;
|
|
18
|
+
const flow = (0, quantam_async_1.quantam)()
|
|
19
|
+
.name('user-flow')
|
|
20
|
+
.step(async (userId) => {
|
|
21
|
+
for (let i = 0; i < requestsPerUser; i += 1) {
|
|
22
|
+
const response = await fetch(url);
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`user ${userId} request ${i} failed with status ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
await response.text();
|
|
27
|
+
}
|
|
28
|
+
return userId;
|
|
29
|
+
})
|
|
30
|
+
.retry(3, 50)
|
|
31
|
+
.timeout(30000);
|
|
32
|
+
const inputs = [];
|
|
33
|
+
for (let i = 0; i < userCount; i += 1) {
|
|
34
|
+
inputs.push(i);
|
|
35
|
+
}
|
|
36
|
+
const start = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
await flow.runMany(inputs, {
|
|
39
|
+
concurrency: maxConcurrentUsers,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error('Quantam runMany error', err);
|
|
44
|
+
}
|
|
45
|
+
const durationSec = (Date.now() - start) / 1000;
|
|
46
|
+
const totalRequests = userCount * requestsPerUser;
|
|
47
|
+
const rps = totalRequests / durationSec;
|
|
48
|
+
console.log(`Quantam bench: users=${userCount}, requestsPerUser=${requestsPerUser}, ` +
|
|
49
|
+
`totalRequests=${totalRequests}, duration=${durationSec.toFixed(2)}s, rps=${rps.toFixed(0)}, concurrency=${maxConcurrentUsers}`);
|
|
50
|
+
await app.close();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
run().catch((err) => {
|
|
54
|
+
console.error(err);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
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: 40,
|
|
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', (ctx) => {
|
|
35
|
+
ctx.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 warmup (1/2)...');
|
|
40
|
+
await runAutocannon(url);
|
|
41
|
+
console.log('Running benchmark (2/2)...');
|
|
42
|
+
const result = await runAutocannon(url);
|
|
43
|
+
autocannon_1.default.printResult(result);
|
|
44
|
+
const totalRequests = result.requests.total;
|
|
45
|
+
const sent = result.requests.sent;
|
|
46
|
+
const avgRps = result.requests.average.toFixed(0);
|
|
47
|
+
const p99 = result.latency.p99.toFixed(1);
|
|
48
|
+
const connections = result.connections;
|
|
49
|
+
const pipelining = result.pipelining;
|
|
50
|
+
// Summary line that shows up clearly in CI/dev logs
|
|
51
|
+
console.log(`QHTTPX bench: total=${totalRequests} (sent=${sent}) req, ` +
|
|
52
|
+
`${avgRps} req/sec, p99=${p99}ms, connections=${connections}, ` +
|
|
53
|
+
`pipelining=${pipelining}`);
|
|
54
|
+
await app.close();
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
run().catch((err) => {
|
|
58
|
+
console.error(err);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
async function runAutocannon(name, url) {
|
|
9
|
+
const result = await (0, autocannon_1.default)({
|
|
10
|
+
url,
|
|
11
|
+
connections: 200,
|
|
12
|
+
pipelining: 10,
|
|
13
|
+
duration: 10,
|
|
14
|
+
});
|
|
15
|
+
const total = result.requests.total;
|
|
16
|
+
const sent = result.requests.sent;
|
|
17
|
+
const rps = result.requests.average;
|
|
18
|
+
const p99 = result.latency.p99;
|
|
19
|
+
console.log(`${name} bench: total=${total} (sent=${sent}) req, ` +
|
|
20
|
+
`${rps.toFixed(0)} req/sec, p99=${p99.toFixed(1)}ms, connections=${result.connections}, pipelining=${result.pipelining}`);
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
total,
|
|
24
|
+
sent,
|
|
25
|
+
rps,
|
|
26
|
+
p99,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function startQHTTPXBalanced() {
|
|
30
|
+
const payloadBuffer = Buffer.from(JSON.stringify({ message: 'hello from qhttpx' }));
|
|
31
|
+
const app = new index_1.QHTTPX({
|
|
32
|
+
maxConcurrency: 1024,
|
|
33
|
+
metricsEnabled: false,
|
|
34
|
+
performanceMode: 'balanced',
|
|
35
|
+
jsonSerializer: () => payloadBuffer,
|
|
36
|
+
});
|
|
37
|
+
app.get('/json', (ctx) => {
|
|
38
|
+
ctx.json({ message: 'hello from qhttpx' });
|
|
39
|
+
});
|
|
40
|
+
const { port } = await app.listen(0, '127.0.0.1');
|
|
41
|
+
const url = `http://127.0.0.1:${port}/json`;
|
|
42
|
+
return { app, url };
|
|
43
|
+
}
|
|
44
|
+
async function startQHTTPXUltra() {
|
|
45
|
+
const payloadBuffer = Buffer.from(JSON.stringify({ message: 'hello from qhttpx ultra' }));
|
|
46
|
+
const app = new index_1.QHTTPX({
|
|
47
|
+
maxConcurrency: 1024,
|
|
48
|
+
performanceMode: 'ultra',
|
|
49
|
+
jsonSerializer: () => payloadBuffer,
|
|
50
|
+
});
|
|
51
|
+
app.get('/json', (ctx) => {
|
|
52
|
+
ctx.json({ message: 'hello from qhttpx ultra' });
|
|
53
|
+
});
|
|
54
|
+
const { port } = await app.listen(0, '127.0.0.1');
|
|
55
|
+
const url = `http://127.0.0.1:${port}/json`;
|
|
56
|
+
return { app, url };
|
|
57
|
+
}
|
|
58
|
+
async function run() {
|
|
59
|
+
const results = [];
|
|
60
|
+
console.log('=== QHTTPX Balanced Mode ===');
|
|
61
|
+
const balanced = await startQHTTPXBalanced();
|
|
62
|
+
try {
|
|
63
|
+
const r = await runAutocannon('QHTTPX-Balanced', balanced.url);
|
|
64
|
+
results.push(r);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await balanced.app.close();
|
|
68
|
+
}
|
|
69
|
+
console.log('\n=== QHTTPX Ultra Mode ===');
|
|
70
|
+
const ultra = await startQHTTPXUltra();
|
|
71
|
+
try {
|
|
72
|
+
const r = await runAutocannon('QHTTPX-Ultra', ultra.url);
|
|
73
|
+
results.push(r);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await ultra.app.close();
|
|
77
|
+
}
|
|
78
|
+
console.log('\n=== Summary ===');
|
|
79
|
+
for (const r of results) {
|
|
80
|
+
console.log(`${r.name}: ${r.rps.toFixed(0)} req/sec, p99=${r.p99.toFixed(1)}ms, total=${r.total}`);
|
|
81
|
+
}
|
|
82
|
+
// Calculate improvement
|
|
83
|
+
const balanced_rps = results[0]?.rps || 0;
|
|
84
|
+
const ultra_rps = results[1]?.rps || 0;
|
|
85
|
+
if (ultra_rps > 0 && balanced_rps > 0) {
|
|
86
|
+
const improvement = ((ultra_rps - balanced_rps) / balanced_rps * 100).toFixed(1);
|
|
87
|
+
console.log(`\nUltra vs Balanced improvement: ${improvement}%`);
|
|
88
|
+
}
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
run().catch((err) => {
|
|
92
|
+
console.error(err);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Buffer pool for response bodies (small, medium, large).
|
|
4
|
+
* Reuses allocated buffers to reduce GC pressure.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.BufferPool = void 0;
|
|
8
|
+
class BufferPool {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.smallBuffers = [];
|
|
11
|
+
this.mediumBuffers = [];
|
|
12
|
+
this.largeBuffers = [];
|
|
13
|
+
this.smallSize = config.smallSize ?? 4096; // 4KB
|
|
14
|
+
this.mediumSize = config.mediumSize ?? 65536; // 64KB
|
|
15
|
+
this.largeSize = config.largeSize ?? 262144; // 256KB
|
|
16
|
+
this.smallPoolSize = config.smallPoolSize ?? 32;
|
|
17
|
+
this.mediumPoolSize = config.mediumPoolSize ?? 8;
|
|
18
|
+
this.largePoolSize = config.largePoolSize ?? 2;
|
|
19
|
+
// Preallocate buffers
|
|
20
|
+
for (let i = 0; i < this.smallPoolSize; i += 1) {
|
|
21
|
+
this.smallBuffers.push(Buffer.allocUnsafe(this.smallSize));
|
|
22
|
+
}
|
|
23
|
+
for (let i = 0; i < this.mediumPoolSize; i += 1) {
|
|
24
|
+
this.mediumBuffers.push(Buffer.allocUnsafe(this.mediumSize));
|
|
25
|
+
}
|
|
26
|
+
for (let i = 0; i < this.largePoolSize; i += 1) {
|
|
27
|
+
this.largeBuffers.push(Buffer.allocUnsafe(this.largeSize));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Acquire a buffer suitable for the given size.
|
|
32
|
+
* Returns a buffer from the appropriate pool.
|
|
33
|
+
*/
|
|
34
|
+
acquire(size) {
|
|
35
|
+
if (size <= this.smallSize) {
|
|
36
|
+
return this.smallBuffers.pop() || Buffer.allocUnsafe(this.smallSize);
|
|
37
|
+
}
|
|
38
|
+
if (size <= this.mediumSize) {
|
|
39
|
+
return this.mediumBuffers.pop() || Buffer.allocUnsafe(this.mediumSize);
|
|
40
|
+
}
|
|
41
|
+
return this.largeBuffers.pop() || Buffer.allocUnsafe(this.largeSize);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Release a buffer back to the appropriate pool.
|
|
45
|
+
*/
|
|
46
|
+
release(buffer) {
|
|
47
|
+
if (buffer.length === this.smallSize && this.smallBuffers.length < this.smallPoolSize) {
|
|
48
|
+
this.smallBuffers.push(buffer);
|
|
49
|
+
}
|
|
50
|
+
else if (buffer.length === this.mediumSize &&
|
|
51
|
+
this.mediumBuffers.length < this.mediumPoolSize) {
|
|
52
|
+
this.mediumBuffers.push(buffer);
|
|
53
|
+
}
|
|
54
|
+
else if (buffer.length === this.largeSize &&
|
|
55
|
+
this.largeBuffers.length < this.largePoolSize) {
|
|
56
|
+
this.largeBuffers.push(buffer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the number of available buffers in each pool.
|
|
61
|
+
*/
|
|
62
|
+
getPoolStatus() {
|
|
63
|
+
return {
|
|
64
|
+
small: this.smallBuffers.length,
|
|
65
|
+
medium: this.mediumBuffers.length,
|
|
66
|
+
large: this.largeBuffers.length,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.BufferPool = BufferPool;
|