qhttpx 1.8.2 ā 1.8.4
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 +18 -0
- package/README.md +33 -22
- package/assets/logo.svg +24 -10
- package/dist/examples/api-server.d.ts +1 -0
- package/dist/examples/api-server.js +56 -0
- package/dist/package.json +1 -1
- package/dist/src/benchmarks/quantam-users.d.ts +1 -0
- package/dist/src/benchmarks/simple-json.d.ts +1 -0
- package/dist/src/benchmarks/ultra-mode.d.ts +1 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/client/index.d.ts +17 -0
- package/dist/src/core/batch.d.ts +24 -0
- package/dist/src/core/body-parser.d.ts +15 -0
- package/dist/src/core/buffer-pool.d.ts +41 -0
- package/dist/src/core/config.d.ts +7 -0
- package/dist/src/core/fusion.d.ts +14 -0
- package/dist/src/core/logger.d.ts +22 -0
- package/dist/src/core/metrics.d.ts +45 -0
- package/dist/src/core/resources.d.ts +9 -0
- package/dist/src/core/scheduler.d.ts +34 -0
- package/dist/src/core/scope.d.ts +26 -0
- package/dist/src/core/serializer.d.ts +10 -0
- package/dist/src/core/server.d.ts +90 -0
- package/dist/src/core/server.js +152 -106
- package/dist/src/core/stream.d.ts +15 -0
- package/dist/src/core/tasks.d.ts +29 -0
- package/dist/src/core/types.d.ts +135 -0
- package/dist/src/core/websocket.d.ts +25 -0
- package/dist/src/core/worker-queue.d.ts +41 -0
- package/dist/src/database/adapters/memory.d.ts +21 -0
- package/dist/src/database/adapters/mongo.d.ts +11 -0
- package/dist/src/database/adapters/postgres.d.ts +10 -0
- package/dist/src/database/adapters/sqlite.d.ts +10 -0
- package/dist/src/database/coalescer.d.ts +14 -0
- package/dist/src/database/manager.d.ts +35 -0
- package/dist/src/database/types.d.ts +20 -0
- package/dist/src/index.d.ts +45 -0
- package/dist/src/index.js +8 -1
- package/dist/src/middleware/compression.d.ts +6 -0
- package/dist/src/middleware/cors.d.ts +11 -0
- package/dist/src/middleware/presets.d.ts +13 -0
- package/dist/src/middleware/rate-limit.d.ts +32 -0
- package/dist/src/middleware/security.d.ts +22 -0
- package/dist/src/middleware/static.d.ts +11 -0
- package/dist/src/openapi/generator.d.ts +19 -0
- package/dist/src/router/radix-router.d.ts +18 -0
- package/dist/src/router/radix-tree.d.ts +16 -0
- package/dist/src/router/router.d.ts +33 -0
- package/dist/src/testing/index.d.ts +25 -0
- package/dist/src/utils/cookies.d.ts +3 -0
- package/dist/src/utils/logger.d.ts +12 -0
- package/dist/src/utils/signals.d.ts +6 -0
- package/dist/src/utils/sse.d.ts +6 -0
- package/dist/src/validation/index.d.ts +3 -0
- package/dist/src/validation/simple.d.ts +5 -0
- package/dist/src/validation/types.d.ts +32 -0
- package/dist/src/validation/zod.d.ts +4 -0
- package/dist/src/views/index.d.ts +1 -0
- package/dist/src/views/types.d.ts +3 -0
- package/dist/tests/adapters.test.d.ts +1 -0
- package/dist/tests/batch.test.d.ts +1 -0
- package/dist/tests/body-parser.test.d.ts +1 -0
- package/dist/tests/compression-sse.test.d.ts +1 -0
- package/dist/tests/cookies.test.d.ts +1 -0
- package/dist/tests/cors.test.d.ts +1 -0
- package/dist/tests/database.test.d.ts +1 -0
- package/dist/tests/dx.test.d.ts +1 -0
- package/dist/tests/dx.test.js +100 -50
- package/dist/tests/ecosystem.test.d.ts +1 -0
- package/dist/tests/features.test.d.ts +1 -0
- package/dist/tests/fusion.test.d.ts +1 -0
- package/dist/tests/http-basic.test.d.ts +1 -0
- package/dist/tests/logger.test.d.ts +1 -0
- package/dist/tests/middleware.test.d.ts +1 -0
- package/dist/tests/observability.test.d.ts +1 -0
- package/dist/tests/openapi.test.d.ts +1 -0
- package/dist/tests/plugin.test.d.ts +1 -0
- package/dist/tests/plugins.test.d.ts +1 -0
- package/dist/tests/rate-limit.test.d.ts +1 -0
- package/dist/tests/resources.test.d.ts +1 -0
- package/dist/tests/scheduler.test.d.ts +1 -0
- package/dist/tests/schema-routes.test.d.ts +1 -0
- package/dist/tests/security.test.d.ts +1 -0
- package/dist/tests/server-db.test.d.ts +1 -0
- package/dist/tests/smoke.test.d.ts +1 -0
- package/dist/tests/sqlite-fusion.test.d.ts +1 -0
- package/dist/tests/static.test.d.ts +1 -0
- package/dist/tests/stream.test.d.ts +1 -0
- package/dist/tests/task-metrics.test.d.ts +1 -0
- package/dist/tests/tasks.test.d.ts +1 -0
- package/dist/tests/testing.test.d.ts +1 -0
- package/dist/tests/validation.test.d.ts +1 -0
- package/dist/tests/websocket.test.d.ts +1 -0
- package/dist/vitest.config.d.ts +2 -0
- package/examples/api-server.ts +44 -236
- package/package.json +1 -1
- package/src/core/server.ts +221 -103
- package/src/core/types.ts +17 -4
- package/src/index.ts +8 -0
- package/tests/dx.test.ts +109 -57
- package/tsconfig.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/examples/api-server.ts
CHANGED
|
@@ -1,254 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IncomingMessage } from 'http';
|
|
3
|
-
import { Duplex } from 'stream';
|
|
4
|
-
import { createHttpApp, HttpError } from '../src/index';
|
|
1
|
+
import { createHttpApp } from '../src/index';
|
|
5
2
|
|
|
6
|
-
// 1. Initialize
|
|
7
|
-
const app = createHttpApp({
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
// 1. Initialize App (Fusion + Aegis enabled)
|
|
4
|
+
const app = createHttpApp({
|
|
5
|
+
enableRequestFusion: true, // ā” Auto-coalesce duplicate requests
|
|
6
|
+
metricsEnabled: true // š Expose /__qhttpx/metrics
|
|
10
7
|
});
|
|
11
8
|
|
|
12
|
-
// 2.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
users.set('1', { id: '1', name: 'Alice', role: 'admin' });
|
|
17
|
-
users.set('2', { id: '2', name: 'Bob', role: 'user' });
|
|
18
|
-
|
|
19
|
-
// 3. Register Background Tasks
|
|
20
|
-
app.task('send-email', async (payload: any) => {
|
|
21
|
-
// Simulate heavy work
|
|
22
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
23
|
-
console.log(`[Background Job] Sending email to ${payload.email}: "${payload.subject}"`);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// 4. Define Routes
|
|
27
|
-
|
|
28
|
-
// Root
|
|
29
|
-
app.get('/', (ctx) => {
|
|
30
|
-
ctx.json({
|
|
31
|
-
message: 'Welcome to QHTTPX Example API',
|
|
32
|
-
endpoints: [
|
|
33
|
-
'GET /api/users',
|
|
34
|
-
'POST /api/users',
|
|
35
|
-
'GET /api/users/:id',
|
|
36
|
-
'POST /api/jobs/email',
|
|
37
|
-
'WS /ws',
|
|
38
|
-
'GET /api/cookies',
|
|
39
|
-
'GET /api/redirect'
|
|
40
|
-
],
|
|
41
|
-
documentation: 'https://github.com/your-repo/qhttpx'
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
app.get('/api/cookies', (ctx) => {
|
|
46
|
-
// Read cookies
|
|
47
|
-
const visited = parseInt(ctx.cookies['visited'] || '0');
|
|
48
|
-
|
|
49
|
-
// Set cookie
|
|
50
|
-
ctx.setCookie('visited', (visited + 1).toString(), {
|
|
51
|
-
path: '/',
|
|
52
|
-
maxAge: 3600, // 1 hour
|
|
53
|
-
httpOnly: true,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
ctx.json({
|
|
57
|
-
message: 'Cookie demo',
|
|
58
|
-
visited: visited + 1,
|
|
59
|
-
allCookies: ctx.cookies
|
|
60
|
-
});
|
|
9
|
+
// 2. Global Middleware (Logging & Rate Limit)
|
|
10
|
+
app.use(async ({ req, next }) => {
|
|
11
|
+
console.log(`[${req.method}] ${req.url}`);
|
|
12
|
+
if (next) await next();
|
|
61
13
|
});
|
|
62
14
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.get((ctx) => {
|
|
70
|
-
const userList = Array.from(users.values());
|
|
71
|
-
ctx.json({ data: userList, count: userList.length });
|
|
72
|
-
})
|
|
73
|
-
.post(async (ctx) => {
|
|
74
|
-
if (!ctx.body || typeof ctx.body !== 'object') {
|
|
75
|
-
ctx.json({ error: 'Invalid body' }, 400);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const body = ctx.body as any;
|
|
80
|
-
if (!body.name || !body.role) {
|
|
81
|
-
ctx.json({ error: 'Missing name or role' }, 400);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const id = (users.size + 1).toString();
|
|
86
|
-
const newUser = { id, name: body.name, role: body.role };
|
|
87
|
-
users.set(id, newUser);
|
|
88
|
-
|
|
89
|
-
ctx.json({ message: 'User created', user: newUser }, 201);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
app.route('/api/users/:id')
|
|
93
|
-
.get((ctx) => {
|
|
94
|
-
const { id } = ctx.params;
|
|
95
|
-
const user = users.get(id);
|
|
96
|
-
|
|
97
|
-
if (!user) {
|
|
98
|
-
ctx.json({ error: 'User not found' }, 404);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
ctx.json({ data: user });
|
|
103
|
-
})
|
|
104
|
-
.delete((ctx) => {
|
|
105
|
-
const { id } = ctx.params;
|
|
106
|
-
if (users.delete(id)) {
|
|
107
|
-
ctx.json({ message: 'User deleted' });
|
|
108
|
-
} else {
|
|
109
|
-
ctx.json({ error: 'User not found' }, 404);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Job Route
|
|
114
|
-
app.post('/api/jobs/email', async (ctx) => {
|
|
115
|
-
const body = ctx.body as any;
|
|
116
|
-
if (!body.email) {
|
|
117
|
-
ctx.json({ error: 'Email required' }, 400);
|
|
118
|
-
return;
|
|
15
|
+
// 3. Validation Schema
|
|
16
|
+
const UserSchema = {
|
|
17
|
+
body: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
required: ['name', 'role'],
|
|
20
|
+
properties: { name: { type: 'string' }, role: { type: 'string' } }
|
|
119
21
|
}
|
|
22
|
+
};
|
|
120
23
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
subject: 'Welcome to QHTTPX!'
|
|
124
|
-
});
|
|
24
|
+
// 4. Routes (Clean & Destructured)
|
|
25
|
+
app.get('/', ({ json }) => json({ status: 'online', fusion: true }));
|
|
125
26
|
|
|
126
|
-
|
|
27
|
+
// ā” Fused Endpoint: 1000 concurrent requests -> 1 DB execution
|
|
28
|
+
app.get('/heavy', async ({ json }) => {
|
|
29
|
+
await new Promise(r => setTimeout(r, 100)); // Simulate DB
|
|
30
|
+
json({ data: 'Expensive Result', timestamp: Date.now() });
|
|
127
31
|
});
|
|
128
32
|
|
|
129
|
-
//
|
|
130
|
-
app.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
app.get('/error', () => {
|
|
134
|
-
throw new Error('Something went wrong!');
|
|
33
|
+
// š”ļø Validated & Typed Route
|
|
34
|
+
app.post('/users', { schema: UserSchema }, async ({ body, json }) => {
|
|
35
|
+
// Body is already validated and typed here
|
|
36
|
+
json({ created: true, user: body }, 201);
|
|
135
37
|
});
|
|
136
38
|
|
|
137
|
-
//
|
|
138
|
-
app.
|
|
139
|
-
|
|
39
|
+
// š File Uploads (Typed)
|
|
40
|
+
app.post('/upload', ({ files, json }) => {
|
|
41
|
+
if (!files?.doc) return json({ error: 'No file' }, 400);
|
|
42
|
+
const doc = Array.isArray(files.doc) ? files.doc[0] : files.doc;
|
|
43
|
+
json({ filename: doc.filename, size: doc.size });
|
|
140
44
|
});
|
|
141
45
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
46
|
+
// š” WebSockets (Pub/Sub)
|
|
47
|
+
app.upgrade('/chat', (ws) => {
|
|
48
|
+
ws.join('general'); // Auto-join room
|
|
49
|
+
ws.on('message', (msg) => {
|
|
50
|
+
// Broadcast to room
|
|
51
|
+
app.websocket.to('general').emit(`Echo: ${msg}`);
|
|
52
|
+
});
|
|
148
53
|
});
|
|
149
54
|
|
|
150
|
-
// 5.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.log(` Try: curl http://localhost:${port}/api/users`);
|
|
55
|
+
// 5. Unified Error Handling
|
|
56
|
+
app.onError(({ error, json }) => {
|
|
57
|
+
console.error(error); // Log internal error
|
|
58
|
+
json({ error: 'Internal Server Error', handled: true }, 500);
|
|
155
59
|
});
|
|
156
60
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
function createWebSocketAccept(key: string): string {
|
|
160
|
-
return createHash('sha1')
|
|
161
|
-
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
162
|
-
.digest('base64');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function handleWebSocketEcho(req: IncomingMessage, socket: Duplex, head: Buffer) {
|
|
166
|
-
void head;
|
|
167
|
-
|
|
168
|
-
const rawKeyHeader = req.headers['sec-websocket-key'];
|
|
169
|
-
let keyHeader: string | undefined;
|
|
170
|
-
if (typeof rawKeyHeader === 'string') {
|
|
171
|
-
keyHeader = rawKeyHeader;
|
|
172
|
-
} else if (Array.isArray(rawKeyHeader)) {
|
|
173
|
-
const values = rawKeyHeader as string[];
|
|
174
|
-
if (values.length > 0) {
|
|
175
|
-
keyHeader = values[0];
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const key = keyHeader ?? '';
|
|
180
|
-
const accept = createWebSocketAccept(key);
|
|
181
|
-
const responseLines = [
|
|
182
|
-
'HTTP/1.1 101 Switching Protocols',
|
|
183
|
-
'Upgrade: websocket',
|
|
184
|
-
'Connection: Upgrade',
|
|
185
|
-
`Sec-WebSocket-Accept: ${accept}`,
|
|
186
|
-
'',
|
|
187
|
-
'',
|
|
188
|
-
];
|
|
189
|
-
socket.write(responseLines.join('\r\n'));
|
|
190
|
-
|
|
191
|
-
socket.on('data', (buffer) => {
|
|
192
|
-
if (buffer.length < 2) return;
|
|
193
|
-
|
|
194
|
-
const opcode = buffer[0] & 0x0f;
|
|
195
|
-
const masked = (buffer[1] & 0x80) !== 0;
|
|
196
|
-
let payloadLength = buffer[1] & 0x7f;
|
|
197
|
-
let offset = 2;
|
|
198
|
-
|
|
199
|
-
if (payloadLength === 126) {
|
|
200
|
-
if (buffer.length < offset + 2) return;
|
|
201
|
-
payloadLength = buffer.readUInt16BE(offset);
|
|
202
|
-
offset += 2;
|
|
203
|
-
} else if (payloadLength === 127) {
|
|
204
|
-
return; // Too large for this demo
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (buffer.length < offset + payloadLength) return;
|
|
208
|
-
|
|
209
|
-
let maskingKey: Buffer | undefined;
|
|
210
|
-
if (masked) {
|
|
211
|
-
maskingKey = buffer.subarray(offset, offset + 4);
|
|
212
|
-
offset += 4;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const payload = buffer.subarray(offset, offset + payloadLength);
|
|
216
|
-
|
|
217
|
-
if (masked && maskingKey) {
|
|
218
|
-
for (let i = 0; i < payload.length; i += 1) {
|
|
219
|
-
payload[i] ^= maskingKey[i % 4];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (opcode === 0x8) { // Close
|
|
224
|
-
socket.end();
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (opcode === 0x1) { // Text
|
|
229
|
-
const message = payload.toString('utf8');
|
|
230
|
-
const textBytes = Buffer.from(message, 'utf8');
|
|
231
|
-
const length = textBytes.length;
|
|
232
|
-
|
|
233
|
-
let header: Buffer;
|
|
234
|
-
if (length < 126) {
|
|
235
|
-
header = Buffer.alloc(2);
|
|
236
|
-
header[0] = 0x81;
|
|
237
|
-
header[1] = length;
|
|
238
|
-
} else if (length < 65536) {
|
|
239
|
-
header = Buffer.alloc(4);
|
|
240
|
-
header[0] = 0x81;
|
|
241
|
-
header[1] = 126;
|
|
242
|
-
header.writeUInt16BE(length, 2);
|
|
243
|
-
} else {
|
|
244
|
-
return; // Too large
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const frame = Buffer.concat([header, textBytes]);
|
|
248
|
-
socket.write(frame);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
socket.on('end', () => socket.end());
|
|
253
|
-
socket.on('error', () => socket.destroy());
|
|
254
|
-
}
|
|
61
|
+
// 6. Start Server
|
|
62
|
+
app.listen(3000, () => console.log('š Server running on http://localhost:3000'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qhttpx",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
4
4
|
"description": "The High-Performance Hybrid HTTP Runtime for Node.js. Built for extreme concurrency, request fusion, and zero-overhead scaling.",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|