qhttpx 1.8.3 → 1.8.5

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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.8.5] - 2026-01-20
6
+ **"The Usability IV Update"**
7
+
8
+ ### Added
9
+ - **Context Headers**: Added `ctx.headers` (alias for `req.headers`) for direct access to request headers.
10
+ - **Environment**: Added `dotenv` and updated `@types/node` dependencies.
11
+
12
+ ## [1.8.4] - 2026-01-20
13
+ **"The Usability III Update"**
14
+
15
+ ### Added
16
+ - **Flexible Route Signatures**: Routes now support `(path, options, handler)` signature for cleaner validation configuration.
17
+ - **Simplified Listen**: `app.listen(port, callback)` is now supported, matching standard patterns.
18
+ - **Updated Examples**: `examples/api-server.ts` rewritten to demonstrate the "Rapid Way" (Fusion, Validation, WebSockets, Typed Files).
19
+
5
20
  ## [1.8.3] - 2026-01-20
6
21
  **"The Developer Experience II Update"**
7
22
 
package/README.md CHANGED
@@ -77,30 +77,42 @@ npm install qhttpx
77
77
 
78
78
  ## ⚡ Quick Start
79
79
 
80
- ### 1. The Standard Way
81
- Clean, explicit, and scalable from day one.
80
+ ### 1. The Rapid Way (Recommended)
81
+ Get up and running instantly with the singleton instance and destructuring support.
82
82
 
83
83
  ```typescript
84
- // Import the framework (Default Export supported)
85
- import QHTTPX from "qhttpx";
84
+ import { app } from "qhttpx";
86
85
 
87
- // Create app (Automatically includes: CORS, Security Headers, Logging, Body Parsing)
88
- const app = QHTTPX();
86
+ // Concise, destructured handlers
87
+ app.get("/", ({ json }) => json({ message: "Hello World" }));
89
88
 
90
- app.get("/", (ctx) => {
91
- return ctx.json({ message: "Welcome to QHTTPX" });
89
+ // Unified error handling
90
+ app.onError(({ error, json }) => {
91
+ json({ error: "Something went wrong", details: error }, 500);
92
92
  });
93
93
 
94
- // Async validation with Zod support
95
- app.post("/users", async (ctx) => {
96
- const body = await ctx.body();
97
- return ctx.status(201).json({ created: true, body });
94
+ // Start listening
95
+ app.listen(3000, () => console.log("Server running on http://localhost:3000"));
96
+ ```
97
+
98
+ ### 2. The Custom Way
99
+ When you need specific configuration options or multiple instances.
100
+
101
+ ```typescript
102
+ import { createHttpApp } from "qhttpx";
103
+
104
+ const app = createHttpApp({
105
+ enableRequestFusion: true, // Enable coalescing
106
+ maxBodySize: "1mb"
98
107
  });
99
108
 
100
- // Start the server
101
- app.listen(3000).then(({ port }) => {
102
- console.log(`Server running on http://localhost:${port}`);
109
+ app.get("/users", async ({ json, query }) => {
110
+ const { page } = query;
111
+ // ...
112
+ json({ page });
103
113
  });
114
+
115
+ app.listen(3000);
104
116
  ```
105
117
 
106
118
  ### 2. The Scalable Way (Cluster Mode)
@@ -143,13 +155,12 @@ app.get('/events', (ctx) => {
143
155
  });
144
156
 
145
157
  // WebSockets with Rooms
146
- app.websocket('/chat', {
147
- open(ws) {
148
- ws.subscribe('general');
149
- },
150
- message(ws, msg) {
151
- ws.publish('general', msg); // Broadcast to everyone in 'general'
152
- }
158
+ app.upgrade('/chat', (ws) => {
159
+ ws.join('general');
160
+ ws.on('message', (msg) => {
161
+ // Broadcast to 'general' room
162
+ app.websocket.to('general').emit(`Echo: ${msg}`);
163
+ });
153
164
  });
154
165
  ```
155
166
 
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../src/index");
4
+ // 1. Initialize App (Fusion + Aegis enabled)
5
+ const app = (0, index_1.createHttpApp)({
6
+ enableRequestFusion: true, // ⚡ Auto-coalesce duplicate requests
7
+ metricsEnabled: true // 📊 Expose /__qhttpx/metrics
8
+ });
9
+ // 2. Global Middleware (Logging & Rate Limit)
10
+ app.use(async ({ req, next }) => {
11
+ console.log(`[${req.method}] ${req.url}`);
12
+ if (next)
13
+ await next();
14
+ });
15
+ // 3. Validation Schema
16
+ const UserSchema = {
17
+ body: {
18
+ type: 'object',
19
+ required: ['name', 'role'],
20
+ properties: { name: { type: 'string' }, role: { type: 'string' } }
21
+ }
22
+ };
23
+ // 4. Routes (Clean & Destructured)
24
+ app.get('/', ({ json }) => json({ status: 'online', fusion: true }));
25
+ // ⚡ Fused Endpoint: 1000 concurrent requests -> 1 DB execution
26
+ app.get('/heavy', async ({ json }) => {
27
+ await new Promise(r => setTimeout(r, 100)); // Simulate DB
28
+ json({ data: 'Expensive Result', timestamp: Date.now() });
29
+ });
30
+ // 🛡️ Validated & Typed Route
31
+ app.post('/users', { schema: UserSchema }, async ({ body, json }) => {
32
+ // Body is already validated and typed here
33
+ json({ created: true, user: body }, 201);
34
+ });
35
+ // 📂 File Uploads (Typed)
36
+ app.post('/upload', ({ files, json }) => {
37
+ if (!files?.doc)
38
+ return json({ error: 'No file' }, 400);
39
+ const doc = Array.isArray(files.doc) ? files.doc[0] : files.doc;
40
+ json({ filename: doc.filename, size: doc.size });
41
+ });
42
+ // 📡 WebSockets (Pub/Sub)
43
+ app.upgrade('/chat', (ws) => {
44
+ ws.join('general'); // Auto-join room
45
+ ws.on('message', (msg) => {
46
+ // Broadcast to room
47
+ app.websocket.to('general').emit(`Echo: ${msg}`);
48
+ });
49
+ });
50
+ // 5. Unified Error Handling
51
+ app.onError(({ error, json }) => {
52
+ console.error(error); // Log internal error
53
+ json({ error: 'Internal Server Error', handled: true }, 500);
54
+ });
55
+ // 6. Start Server
56
+ app.listen(3000, () => console.log('🚀 Server running on http://localhost:3000'));
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qhttpx",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
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",
@@ -67,6 +67,7 @@
67
67
  "@typescript-eslint/eslint-plugin": "^8.53.0",
68
68
  "@typescript-eslint/parser": "^8.53.0",
69
69
  "autocannon": "^8.0.0",
70
+ "dotenv": "^17.2.3",
70
71
  "eslint": "^9.39.2",
71
72
  "eslint-config-prettier": "^10.1.8",
72
73
  "eslint-plugin-prettier": "^5.5.5",
@@ -56,9 +56,13 @@ export declare class QHTTPX {
56
56
  register<Options extends QHTTPXPluginOptions>(plugin: QHTTPXPlugin<Options>, options?: Options): Promise<void>;
57
57
  private registerRoute;
58
58
  get(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
59
+ get(path: string, config: import('./types').QHTTPXRouteConfig, handler: QHTTPXHandler): void;
59
60
  post(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
61
+ post(path: string, config: import('./types').QHTTPXRouteConfig, handler: QHTTPXHandler): void;
60
62
  put(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
63
+ put(path: string, config: import('./types').QHTTPXRouteConfig, handler: QHTTPXHandler): void;
61
64
  delete(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
65
+ delete(path: string, config: import('./types').QHTTPXRouteConfig, handler: QHTTPXHandler): void;
62
66
  route(path: string): {
63
67
  get(handler: QHTTPXHandler | QHTTPXRouteOptions): /*elided*/ any;
64
68
  post(handler: QHTTPXHandler | QHTTPXRouteOptions): /*elided*/ any;
@@ -70,7 +74,7 @@ export declare class QHTTPX {
70
74
  op(name: string, handler: import('./types').QHTTPXOpHandler): void;
71
75
  private registerInternalRoutes;
72
76
  getOpenAPI(options: OpenAPIOptions): object;
73
- listen(port: number, hostname?: string): Promise<{
77
+ listen(port: number, hostnameOrCallback?: string | (() => void), callback?: () => void): Promise<{
74
78
  port: number;
75
79
  }>;
76
80
  close(): Promise<void>;
@@ -280,32 +280,38 @@ class QHTTPX {
280
280
  const scope = new scope_1.QHTTPXScope(this, options?.prefix);
281
281
  await plugin(scope, options);
282
282
  }
283
- registerRoute(method, path, handlerOrOptions) {
283
+ registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
284
284
  let handler;
285
285
  let schema;
286
286
  const options = {};
287
287
  if (typeof handlerOrOptions === 'function') {
288
288
  handler = handlerOrOptions;
289
289
  }
290
- else {
291
- handler = handlerOrOptions.handler;
290
+ else if (handlerIfOptions) {
291
+ handler = handlerIfOptions;
292
292
  schema = handlerOrOptions.schema;
293
293
  options.priority = handlerOrOptions.priority;
294
294
  }
295
+ else {
296
+ const opts = handlerOrOptions;
297
+ handler = opts.handler;
298
+ schema = opts.schema;
299
+ options.priority = opts.priority;
300
+ }
295
301
  const compiled = this.compileRoutePipeline(handler, schema);
296
302
  this.router.register(method, path, compiled, { ...options, schema });
297
303
  }
298
- get(path, handler) {
299
- this.registerRoute('GET', path, handler);
304
+ get(path, handlerOrOptions, handler) {
305
+ this.registerRoute('GET', path, handlerOrOptions, handler);
300
306
  }
301
- post(path, handler) {
302
- this.registerRoute('POST', path, handler);
307
+ post(path, handlerOrOptions, handler) {
308
+ this.registerRoute('POST', path, handlerOrOptions, handler);
303
309
  }
304
- put(path, handler) {
305
- this.registerRoute('PUT', path, handler);
310
+ put(path, handlerOrOptions, handler) {
311
+ this.registerRoute('PUT', path, handlerOrOptions, handler);
306
312
  }
307
- delete(path, handler) {
308
- this.registerRoute('DELETE', path, handler);
313
+ delete(path, handlerOrOptions, handler) {
314
+ this.registerRoute('DELETE', path, handlerOrOptions, handler);
309
315
  }
310
316
  route(path) {
311
317
  const register = (method, handler) => {
@@ -402,7 +408,17 @@ class QHTTPX {
402
408
  const generator = new generator_1.OpenAPIGenerator(this.router, options);
403
409
  return generator.generate();
404
410
  }
405
- async listen(port, hostname) {
411
+ async listen(port, hostnameOrCallback, callback) {
412
+ let hostname;
413
+ let cb;
414
+ if (typeof hostnameOrCallback === 'function') {
415
+ cb = hostnameOrCallback;
416
+ hostname = undefined;
417
+ }
418
+ else {
419
+ hostname = hostnameOrCallback;
420
+ cb = callback;
421
+ }
406
422
  if (this.options.database) {
407
423
  await this.options.database.connect();
408
424
  }
@@ -424,6 +440,8 @@ class QHTTPX {
424
440
  this.router.freeze();
425
441
  void this.runLifecycleHooks(this.onStartHooks);
426
442
  const address = this.server.address();
443
+ if (cb)
444
+ cb();
427
445
  if (address && typeof address === 'object') {
428
446
  resolve({ port: address.port });
429
447
  }
@@ -456,6 +474,7 @@ class QHTTPX {
456
474
  const ctx = {
457
475
  req: null,
458
476
  res: null,
477
+ headers: null,
459
478
  url: null,
460
479
  params: null,
461
480
  query: null,
@@ -571,6 +590,7 @@ class QHTTPX {
571
590
  const mutableCtx = ctx;
572
591
  mutableCtx.req = req;
573
592
  mutableCtx.res = res;
593
+ mutableCtx.headers = req.headers;
574
594
  mutableCtx.url = url;
575
595
  mutableCtx.params = params;
576
596
  mutableCtx.query = query;
@@ -592,6 +612,7 @@ class QHTTPX {
592
612
  const mutableCtx = ctx;
593
613
  mutableCtx.req = null;
594
614
  mutableCtx.res = null;
615
+ mutableCtx.headers = null;
595
616
  mutableCtx.url = null;
596
617
  mutableCtx.params = null;
597
618
  mutableCtx.query = null;
@@ -1,4 +1,4 @@
1
- import { IncomingMessage, ServerResponse } from 'http';
1
+ import { IncomingMessage, ServerResponse, IncomingHttpHeaders } from 'http';
2
2
  import { URL } from 'url';
3
3
  import type { BufferPool, BufferPoolConfig } from './buffer-pool';
4
4
  import type { DatabaseManager } from '../database/manager';
@@ -42,6 +42,7 @@ export type QHTTPXFile = {
42
42
  export type QHTTPXContext = {
43
43
  readonly req: IncomingMessage;
44
44
  readonly res: ServerResponse;
45
+ readonly headers: IncomingHttpHeaders;
45
46
  readonly url: URL;
46
47
  readonly params: Record<string, string>;
47
48
  readonly query: Record<string, string | string[]>;
@@ -73,6 +74,7 @@ export type QHTTPXRouteOptions = {
73
74
  handler: QHTTPXHandler;
74
75
  priority?: RoutePriority;
75
76
  };
77
+ export type QHTTPXRouteConfig = Omit<QHTTPXRouteOptions, 'handler'>;
76
78
  export type QHTTPXMiddleware = (ctx: QHTTPXContext, next: () => Promise<void>) => void | Promise<void>;
77
79
  export type QHTTPXErrorContext = QHTTPXContext & {
78
80
  error: unknown;
@@ -1,254 +1,62 @@
1
- import { createHash } from 'crypto';
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 the application (includes CORS, Security Headers, Logging)
7
- const app = createHttpApp({
8
- metricsEnabled: true,
9
- workers: 1,
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. Mock Database
13
- const users = new Map<string, { id: string; name: string; role: string }>();
14
-
15
- // Seed some data
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
- app.get('/api/redirect', (ctx) => {
64
- ctx.redirect('/api/users');
65
- });
66
-
67
- // User Routes using the Chainable Builder
68
- app.route('/api/users')
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
- await app.enqueue('send-email', {
122
- email: body.email,
123
- subject: 'Welcome to QHTTPX!'
124
- });
24
+ // 4. Routes (Clean & Destructured)
25
+ app.get('/', ({ json }) => json({ status: 'online', fusion: true }));
125
26
 
126
- ctx.json({ message: 'Email job queued', status: 'pending' }, 202);
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
- // WebSocket Route
130
- app.upgrade('/ws', handleWebSocketEcho);
131
-
132
- // Error handling demo
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
- // Custom Error Handlers (optional, defaults are good)
138
- app.setNotFoundHandler((ctx) => {
139
- ctx.json({ error: 'Not Found', path: ctx.url.pathname }, 404);
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
- app.setErrorHandler((err, ctx) => {
143
- if (err instanceof HttpError) {
144
- ctx.json({ error: err.message, code: err.code, details: err.details }, err.status);
145
- return;
146
- }
147
- ctx.json({ error: 'Internal Server Error' }, 500);
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. Start Server
151
- const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
152
- app.listen(PORT, '0.0.0.0').then(({ port }) => {
153
- console.log(`\n🚀 Server running at http://localhost:${port}`);
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
- // --- Helpers ---
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",
3
+ "version": "1.8.5",
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",
@@ -67,6 +67,7 @@
67
67
  "@typescript-eslint/eslint-plugin": "^8.53.0",
68
68
  "@typescript-eslint/parser": "^8.53.0",
69
69
  "autocannon": "^8.0.0",
70
+ "dotenv": "^17.2.3",
70
71
  "eslint": "^9.39.2",
71
72
  "eslint-config-prettier": "^10.1.8",
72
73
  "eslint-plugin-prettier": "^5.5.5",
@@ -395,7 +395,11 @@ export class QHTTPX {
395
395
  private registerRoute(
396
396
  method: HTTPMethod,
397
397
  path: string,
398
- handlerOrOptions: QHTTPXHandler | QHTTPXRouteOptions,
398
+ handlerOrOptions:
399
+ | QHTTPXHandler
400
+ | QHTTPXRouteOptions
401
+ | import('./types').QHTTPXRouteConfig,
402
+ handlerIfOptions?: QHTTPXHandler,
399
403
  ): void {
400
404
  let handler: QHTTPXHandler;
401
405
  let schema: RouteSchema | Record<string, unknown> | undefined;
@@ -403,30 +407,87 @@ export class QHTTPX {
403
407
 
404
408
  if (typeof handlerOrOptions === 'function') {
405
409
  handler = handlerOrOptions;
406
- } else {
407
- handler = handlerOrOptions.handler;
410
+ } else if (handlerIfOptions) {
411
+ handler = handlerIfOptions;
408
412
  schema = handlerOrOptions.schema;
409
413
  options.priority = handlerOrOptions.priority;
414
+ } else {
415
+ const opts = handlerOrOptions as QHTTPXRouteOptions;
416
+ handler = opts.handler;
417
+ schema = opts.schema;
418
+ options.priority = opts.priority;
410
419
  }
411
420
 
412
421
  const compiled = this.compileRoutePipeline(handler, schema);
413
422
  this.router.register(method, path, compiled, { ...options, schema });
414
423
  }
415
424
 
416
- get(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void {
417
- this.registerRoute('GET', path, handler);
425
+ get(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
426
+ get(
427
+ path: string,
428
+ config: import('./types').QHTTPXRouteConfig,
429
+ handler: QHTTPXHandler,
430
+ ): void;
431
+ get(
432
+ path: string,
433
+ handlerOrOptions:
434
+ | QHTTPXHandler
435
+ | QHTTPXRouteOptions
436
+ | import('./types').QHTTPXRouteConfig,
437
+ handler?: QHTTPXHandler,
438
+ ): void {
439
+ this.registerRoute('GET', path, handlerOrOptions, handler);
418
440
  }
419
441
 
420
- post(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void {
421
- this.registerRoute('POST', path, handler);
442
+ post(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
443
+ post(
444
+ path: string,
445
+ config: import('./types').QHTTPXRouteConfig,
446
+ handler: QHTTPXHandler,
447
+ ): void;
448
+ post(
449
+ path: string,
450
+ handlerOrOptions:
451
+ | QHTTPXHandler
452
+ | QHTTPXRouteOptions
453
+ | import('./types').QHTTPXRouteConfig,
454
+ handler?: QHTTPXHandler,
455
+ ): void {
456
+ this.registerRoute('POST', path, handlerOrOptions, handler);
422
457
  }
423
458
 
424
- put(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void {
425
- this.registerRoute('PUT', path, handler);
459
+ put(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
460
+ put(
461
+ path: string,
462
+ config: import('./types').QHTTPXRouteConfig,
463
+ handler: QHTTPXHandler,
464
+ ): void;
465
+ put(
466
+ path: string,
467
+ handlerOrOptions:
468
+ | QHTTPXHandler
469
+ | QHTTPXRouteOptions
470
+ | import('./types').QHTTPXRouteConfig,
471
+ handler?: QHTTPXHandler,
472
+ ): void {
473
+ this.registerRoute('PUT', path, handlerOrOptions, handler);
426
474
  }
427
475
 
428
- delete(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void {
429
- this.registerRoute('DELETE', path, handler);
476
+ delete(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
477
+ delete(
478
+ path: string,
479
+ config: import('./types').QHTTPXRouteConfig,
480
+ handler: QHTTPXHandler,
481
+ ): void;
482
+ delete(
483
+ path: string,
484
+ handlerOrOptions:
485
+ | QHTTPXHandler
486
+ | QHTTPXRouteOptions
487
+ | import('./types').QHTTPXRouteConfig,
488
+ handler?: QHTTPXHandler,
489
+ ): void {
490
+ this.registerRoute('DELETE', path, handlerOrOptions, handler);
430
491
  }
431
492
 
432
493
  route(path: string) {
@@ -541,7 +602,22 @@ export class QHTTPX {
541
602
  return generator.generate();
542
603
  }
543
604
 
544
- public async listen(port: number, hostname?: string): Promise<{ port: number }> {
605
+ public async listen(
606
+ port: number,
607
+ hostnameOrCallback?: string | (() => void),
608
+ callback?: () => void,
609
+ ): Promise<{ port: number }> {
610
+ let hostname: string | undefined;
611
+ let cb: (() => void) | undefined;
612
+
613
+ if (typeof hostnameOrCallback === 'function') {
614
+ cb = hostnameOrCallback;
615
+ hostname = undefined;
616
+ } else {
617
+ hostname = hostnameOrCallback;
618
+ cb = callback;
619
+ }
620
+
545
621
  if (this.options.database) {
546
622
  await this.options.database.connect();
547
623
  }
@@ -567,6 +643,9 @@ export class QHTTPX {
567
643
  this.router.freeze();
568
644
  void this.runLifecycleHooks(this.onStartHooks);
569
645
  const address = this.server.address();
646
+
647
+ if (cb) cb();
648
+
570
649
  if (address && typeof address === 'object') {
571
650
  resolve({ port: address.port });
572
651
  } else {
@@ -601,6 +680,7 @@ export class QHTTPX {
601
680
  const ctx: any = {
602
681
  req: null,
603
682
  res: null,
683
+ headers: null,
604
684
  url: null,
605
685
  params: null,
606
686
  query: null,
@@ -731,6 +811,7 @@ export class QHTTPX {
731
811
  const mutableCtx = ctx as any;
732
812
  mutableCtx.req = req;
733
813
  mutableCtx.res = res;
814
+ mutableCtx.headers = req.headers;
734
815
  mutableCtx.url = url;
735
816
  mutableCtx.params = params;
736
817
  mutableCtx.query = query;
@@ -754,6 +835,7 @@ export class QHTTPX {
754
835
  const mutableCtx = ctx as any;
755
836
  mutableCtx.req = null;
756
837
  mutableCtx.res = null;
838
+ mutableCtx.headers = null;
757
839
  mutableCtx.url = null;
758
840
  mutableCtx.params = null;
759
841
  mutableCtx.query = null;
package/src/core/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IncomingMessage, ServerResponse } from 'http';
1
+ import { IncomingMessage, ServerResponse, IncomingHttpHeaders } from 'http';
2
2
  import { URL } from 'url';
3
3
  import type { BufferPool, BufferPoolConfig } from './buffer-pool';
4
4
  import type { DatabaseManager } from '../database/manager';
@@ -59,6 +59,7 @@ export type QHTTPXFile = {
59
59
  export type QHTTPXContext = {
60
60
  readonly req: IncomingMessage;
61
61
  readonly res: ServerResponse;
62
+ readonly headers: IncomingHttpHeaders;
62
63
  readonly url: URL;
63
64
  readonly params: Record<string, string>;
64
65
  readonly query: Record<string, string | string[]>;
@@ -100,6 +101,9 @@ export type QHTTPXRouteOptions = {
100
101
  priority?: RoutePriority;
101
102
  };
102
103
 
104
+ export type QHTTPXRouteConfig = Omit<QHTTPXRouteOptions, 'handler'>;
105
+
106
+
103
107
  export type QHTTPXMiddleware = (
104
108
  ctx: QHTTPXContext,
105
109
  next: () => Promise<void>,
package/tsconfig.json CHANGED
@@ -13,5 +13,5 @@
13
13
  "declaration": true,
14
14
  "types": ["node", "vitest"]
15
15
  },
16
- "include": ["src", "tests", "vitest.config.*"]
16
+ "include": ["src", "tests", "examples", "vitest.config.*"]
17
17
  }