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.
Files changed (101) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +33 -22
  3. package/assets/logo.svg +24 -10
  4. package/dist/examples/api-server.d.ts +1 -0
  5. package/dist/examples/api-server.js +56 -0
  6. package/dist/package.json +1 -1
  7. package/dist/src/benchmarks/quantam-users.d.ts +1 -0
  8. package/dist/src/benchmarks/simple-json.d.ts +1 -0
  9. package/dist/src/benchmarks/ultra-mode.d.ts +1 -0
  10. package/dist/src/cli/index.d.ts +2 -0
  11. package/dist/src/client/index.d.ts +17 -0
  12. package/dist/src/core/batch.d.ts +24 -0
  13. package/dist/src/core/body-parser.d.ts +15 -0
  14. package/dist/src/core/buffer-pool.d.ts +41 -0
  15. package/dist/src/core/config.d.ts +7 -0
  16. package/dist/src/core/fusion.d.ts +14 -0
  17. package/dist/src/core/logger.d.ts +22 -0
  18. package/dist/src/core/metrics.d.ts +45 -0
  19. package/dist/src/core/resources.d.ts +9 -0
  20. package/dist/src/core/scheduler.d.ts +34 -0
  21. package/dist/src/core/scope.d.ts +26 -0
  22. package/dist/src/core/serializer.d.ts +10 -0
  23. package/dist/src/core/server.d.ts +90 -0
  24. package/dist/src/core/server.js +152 -106
  25. package/dist/src/core/stream.d.ts +15 -0
  26. package/dist/src/core/tasks.d.ts +29 -0
  27. package/dist/src/core/types.d.ts +135 -0
  28. package/dist/src/core/websocket.d.ts +25 -0
  29. package/dist/src/core/worker-queue.d.ts +41 -0
  30. package/dist/src/database/adapters/memory.d.ts +21 -0
  31. package/dist/src/database/adapters/mongo.d.ts +11 -0
  32. package/dist/src/database/adapters/postgres.d.ts +10 -0
  33. package/dist/src/database/adapters/sqlite.d.ts +10 -0
  34. package/dist/src/database/coalescer.d.ts +14 -0
  35. package/dist/src/database/manager.d.ts +35 -0
  36. package/dist/src/database/types.d.ts +20 -0
  37. package/dist/src/index.d.ts +45 -0
  38. package/dist/src/index.js +8 -1
  39. package/dist/src/middleware/compression.d.ts +6 -0
  40. package/dist/src/middleware/cors.d.ts +11 -0
  41. package/dist/src/middleware/presets.d.ts +13 -0
  42. package/dist/src/middleware/rate-limit.d.ts +32 -0
  43. package/dist/src/middleware/security.d.ts +22 -0
  44. package/dist/src/middleware/static.d.ts +11 -0
  45. package/dist/src/openapi/generator.d.ts +19 -0
  46. package/dist/src/router/radix-router.d.ts +18 -0
  47. package/dist/src/router/radix-tree.d.ts +16 -0
  48. package/dist/src/router/router.d.ts +33 -0
  49. package/dist/src/testing/index.d.ts +25 -0
  50. package/dist/src/utils/cookies.d.ts +3 -0
  51. package/dist/src/utils/logger.d.ts +12 -0
  52. package/dist/src/utils/signals.d.ts +6 -0
  53. package/dist/src/utils/sse.d.ts +6 -0
  54. package/dist/src/validation/index.d.ts +3 -0
  55. package/dist/src/validation/simple.d.ts +5 -0
  56. package/dist/src/validation/types.d.ts +32 -0
  57. package/dist/src/validation/zod.d.ts +4 -0
  58. package/dist/src/views/index.d.ts +1 -0
  59. package/dist/src/views/types.d.ts +3 -0
  60. package/dist/tests/adapters.test.d.ts +1 -0
  61. package/dist/tests/batch.test.d.ts +1 -0
  62. package/dist/tests/body-parser.test.d.ts +1 -0
  63. package/dist/tests/compression-sse.test.d.ts +1 -0
  64. package/dist/tests/cookies.test.d.ts +1 -0
  65. package/dist/tests/cors.test.d.ts +1 -0
  66. package/dist/tests/database.test.d.ts +1 -0
  67. package/dist/tests/dx.test.d.ts +1 -0
  68. package/dist/tests/dx.test.js +100 -50
  69. package/dist/tests/ecosystem.test.d.ts +1 -0
  70. package/dist/tests/features.test.d.ts +1 -0
  71. package/dist/tests/fusion.test.d.ts +1 -0
  72. package/dist/tests/http-basic.test.d.ts +1 -0
  73. package/dist/tests/logger.test.d.ts +1 -0
  74. package/dist/tests/middleware.test.d.ts +1 -0
  75. package/dist/tests/observability.test.d.ts +1 -0
  76. package/dist/tests/openapi.test.d.ts +1 -0
  77. package/dist/tests/plugin.test.d.ts +1 -0
  78. package/dist/tests/plugins.test.d.ts +1 -0
  79. package/dist/tests/rate-limit.test.d.ts +1 -0
  80. package/dist/tests/resources.test.d.ts +1 -0
  81. package/dist/tests/scheduler.test.d.ts +1 -0
  82. package/dist/tests/schema-routes.test.d.ts +1 -0
  83. package/dist/tests/security.test.d.ts +1 -0
  84. package/dist/tests/server-db.test.d.ts +1 -0
  85. package/dist/tests/smoke.test.d.ts +1 -0
  86. package/dist/tests/sqlite-fusion.test.d.ts +1 -0
  87. package/dist/tests/static.test.d.ts +1 -0
  88. package/dist/tests/stream.test.d.ts +1 -0
  89. package/dist/tests/task-metrics.test.d.ts +1 -0
  90. package/dist/tests/tasks.test.d.ts +1 -0
  91. package/dist/tests/testing.test.d.ts +1 -0
  92. package/dist/tests/validation.test.d.ts +1 -0
  93. package/dist/tests/websocket.test.d.ts +1 -0
  94. package/dist/vitest.config.d.ts +2 -0
  95. package/examples/api-server.ts +44 -236
  96. package/package.json +1 -1
  97. package/src/core/server.ts +221 -103
  98. package/src/core/types.ts +17 -4
  99. package/src/index.ts +8 -0
  100. package/tests/dx.test.ts +109 -57
  101. package/tsconfig.json +2 -1
@@ -111,6 +111,18 @@ class QHTTPX {
111
111
  set405Handler(handler) {
112
112
  this.setMethodNotAllowedHandler(handler);
113
113
  }
114
+ /**
115
+ * Alias for setErrorHandler
116
+ */
117
+ onError(handler) {
118
+ this.setErrorHandler(handler);
119
+ }
120
+ /**
121
+ * Alias for setNotFoundHandler
122
+ */
123
+ notFound(handler) {
124
+ this.setNotFoundHandler(handler);
125
+ }
114
126
  onStart(hook) {
115
127
  this.onStartHooks.push(hook);
116
128
  }
@@ -239,12 +251,16 @@ class QHTTPX {
239
251
  const middleware = middlewares[i];
240
252
  const next = pipeline;
241
253
  pipeline = (ctx) => {
242
- return middleware(ctx, async () => {
254
+ const nextFn = async () => {
243
255
  const result = next(ctx);
244
256
  if (result && typeof result.then === 'function') {
245
257
  await result;
246
258
  }
247
- });
259
+ };
260
+ // Attach next to ctx for destructuring support
261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
+ ctx.next = nextFn;
263
+ return middleware(ctx, nextFn);
248
264
  };
249
265
  }
250
266
  if (stringifier) {
@@ -264,32 +280,38 @@ class QHTTPX {
264
280
  const scope = new scope_1.QHTTPXScope(this, options?.prefix);
265
281
  await plugin(scope, options);
266
282
  }
267
- registerRoute(method, path, handlerOrOptions) {
283
+ registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
268
284
  let handler;
269
285
  let schema;
270
286
  const options = {};
271
287
  if (typeof handlerOrOptions === 'function') {
272
288
  handler = handlerOrOptions;
273
289
  }
274
- else {
275
- handler = handlerOrOptions.handler;
290
+ else if (handlerIfOptions) {
291
+ handler = handlerIfOptions;
276
292
  schema = handlerOrOptions.schema;
277
293
  options.priority = handlerOrOptions.priority;
278
294
  }
295
+ else {
296
+ const opts = handlerOrOptions;
297
+ handler = opts.handler;
298
+ schema = opts.schema;
299
+ options.priority = opts.priority;
300
+ }
279
301
  const compiled = this.compileRoutePipeline(handler, schema);
280
302
  this.router.register(method, path, compiled, { ...options, schema });
281
303
  }
282
- get(path, handler) {
283
- this.registerRoute('GET', path, handler);
304
+ get(path, handlerOrOptions, handler) {
305
+ this.registerRoute('GET', path, handlerOrOptions, handler);
284
306
  }
285
- post(path, handler) {
286
- this.registerRoute('POST', path, handler);
307
+ post(path, handlerOrOptions, handler) {
308
+ this.registerRoute('POST', path, handlerOrOptions, handler);
287
309
  }
288
- put(path, handler) {
289
- this.registerRoute('PUT', path, handler);
310
+ put(path, handlerOrOptions, handler) {
311
+ this.registerRoute('PUT', path, handlerOrOptions, handler);
290
312
  }
291
- delete(path, handler) {
292
- this.registerRoute('DELETE', path, handler);
313
+ delete(path, handlerOrOptions, handler) {
314
+ this.registerRoute('DELETE', path, handlerOrOptions, handler);
293
315
  }
294
316
  route(path) {
295
317
  const register = (method, handler) => {
@@ -386,7 +408,17 @@ class QHTTPX {
386
408
  const generator = new generator_1.OpenAPIGenerator(this.router, options);
387
409
  return generator.generate();
388
410
  }
389
- 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
+ }
390
422
  if (this.options.database) {
391
423
  await this.options.database.connect();
392
424
  }
@@ -408,6 +440,8 @@ class QHTTPX {
408
440
  this.router.freeze();
409
441
  void this.runLifecycleHooks(this.onStartHooks);
410
442
  const address = this.server.address();
443
+ if (cb)
444
+ cb();
411
445
  if (address && typeof address === 'object') {
412
446
  resolve({ port: address.port });
413
447
  }
@@ -450,95 +484,98 @@ class QHTTPX {
450
484
  requestId: '',
451
485
  requestStart: 0,
452
486
  serializer: null,
453
- json(payload, status = 200) {
454
- const res = this.res;
455
- if (!res.headersSent) {
456
- res.statusCode = status;
457
- res.setHeader('content-type', 'application/json; charset=utf-8');
458
- }
459
- let body;
460
- if (this.serializer) {
461
- body = this.serializer(payload);
462
- }
463
- else if (useFastStringify) {
464
- body = (0, serializer_1.fastJsonStringify)(payload);
465
- }
466
- else if (jsonSerializer) {
467
- body = jsonSerializer(payload);
468
- }
469
- else {
470
- body = JSON.stringify(payload);
471
- }
472
- res.end(body);
473
- },
474
- send(payload, status = 200) {
475
- const res = this.res;
476
- if (!res.headersSent) {
477
- res.statusCode = status;
478
- }
479
- res.end(payload);
480
- },
481
- html(payload, status = 200) {
482
- const res = this.res;
483
- if (!res.headersSent) {
484
- res.statusCode = status;
485
- res.setHeader('content-type', 'text/html; charset=utf-8');
486
- }
487
- res.end(payload);
488
- },
489
- redirect(url, status = 302) {
490
- const res = this.res;
491
- if (!res.headersSent) {
492
- res.statusCode = status;
493
- res.setHeader('Location', url);
494
- }
495
- res.end();
496
- },
497
- setCookie(name, value, options) {
498
- const res = this.res;
499
- const serialized = (0, cookies_1.serializeCookie)(name, value, options);
500
- let existing = res.getHeader('Set-Cookie');
501
- if (Array.isArray(existing)) {
502
- existing.push(serialized);
503
- res.setHeader('Set-Cookie', existing);
504
- }
505
- else if (existing) {
506
- res.setHeader('Set-Cookie', [existing, serialized]);
507
- }
508
- else {
509
- res.setHeader('Set-Cookie', serialized);
510
- }
511
- },
487
+ path: '',
488
+ error: undefined,
512
489
  db: this.options.database,
513
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
514
- render: async (view, locals) => {
515
- const engine = this.options.viewEngine;
516
- if (!engine) {
517
- throw new Error('No view engine registered');
518
- }
519
- const viewsPath = this.options.viewsPath || process.cwd();
520
- const fullPath = path_1.default.resolve(viewsPath, view);
521
- const html = await engine.render(fullPath, locals || {});
522
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
523
- const res = ctx.res;
524
- if (!res.headersSent) {
525
- res.statusCode = 200;
526
- res.setHeader('content-type', 'text/html; charset=utf-8');
527
- }
528
- res.end(html);
529
- },
530
- validate: async (schema, data) => {
531
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
532
- const target = data ?? ctx.body;
533
- const result = await this.validator.validate(schema, target);
534
- if (result.success) {
535
- return result.data;
536
- }
537
- throw new types_1.HttpError(400, 'Validation Error', {
538
- code: 'VALIDATION_ERROR',
539
- details: result.error,
540
- });
541
- },
490
+ };
491
+ // Helper to get response object from closure-captured ctx
492
+ // We use arrow functions to ensure they don't depend on 'this' context at call site
493
+ // enabling destructuring like: ({ json }) => json(...)
494
+ ctx.json = (payload, status = 200) => {
495
+ const res = ctx.res;
496
+ if (!res.headersSent) {
497
+ res.statusCode = status;
498
+ res.setHeader('content-type', 'application/json; charset=utf-8');
499
+ }
500
+ let body;
501
+ if (ctx.serializer) {
502
+ body = ctx.serializer(payload);
503
+ }
504
+ else if (useFastStringify) {
505
+ body = (0, serializer_1.fastJsonStringify)(payload);
506
+ }
507
+ else if (jsonSerializer) {
508
+ body = jsonSerializer(payload);
509
+ }
510
+ else {
511
+ body = JSON.stringify(payload);
512
+ }
513
+ res.end(body);
514
+ };
515
+ ctx.send = (payload, status = 200) => {
516
+ const res = ctx.res;
517
+ if (!res.headersSent) {
518
+ res.statusCode = status;
519
+ }
520
+ res.end(payload);
521
+ };
522
+ ctx.html = (payload, status = 200) => {
523
+ const res = ctx.res;
524
+ if (!res.headersSent) {
525
+ res.statusCode = status;
526
+ res.setHeader('content-type', 'text/html; charset=utf-8');
527
+ }
528
+ res.end(payload);
529
+ };
530
+ ctx.redirect = (url, status = 302) => {
531
+ const res = ctx.res;
532
+ if (!res.headersSent) {
533
+ res.statusCode = status;
534
+ res.setHeader('Location', url);
535
+ }
536
+ res.end();
537
+ };
538
+ ctx.setCookie = (name, value, options) => {
539
+ const res = ctx.res;
540
+ const serialized = (0, cookies_1.serializeCookie)(name, value, options);
541
+ let existing = res.getHeader('Set-Cookie');
542
+ if (Array.isArray(existing)) {
543
+ existing.push(serialized);
544
+ res.setHeader('Set-Cookie', existing);
545
+ }
546
+ else if (existing) {
547
+ res.setHeader('Set-Cookie', [existing, serialized]);
548
+ }
549
+ else {
550
+ res.setHeader('Set-Cookie', serialized);
551
+ }
552
+ };
553
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
554
+ ctx.render = async (view, locals) => {
555
+ const engine = this.options.viewEngine;
556
+ if (!engine) {
557
+ throw new Error('No view engine registered');
558
+ }
559
+ const viewsPath = this.options.viewsPath || process.cwd();
560
+ const fullPath = path_1.default.resolve(viewsPath, view);
561
+ const html = await engine.render(fullPath, locals || {});
562
+ const res = ctx.res;
563
+ if (!res.headersSent) {
564
+ res.statusCode = 200;
565
+ res.setHeader('content-type', 'text/html; charset=utf-8');
566
+ }
567
+ res.end(html);
568
+ };
569
+ ctx.validate = async (schema, data) => {
570
+ const target = data ?? ctx.body;
571
+ const result = await this.validator.validate(schema, target);
572
+ if (result.success) {
573
+ return result.data;
574
+ }
575
+ throw new types_1.HttpError(400, 'Validation Error', {
576
+ code: 'VALIDATION_ERROR',
577
+ details: result.error,
578
+ });
542
579
  };
543
580
  return ctx;
544
581
  }
@@ -565,6 +602,7 @@ class QHTTPX {
565
602
  }
566
603
  mutableCtx.state = {};
567
604
  mutableCtx.disableAutoEnd = false;
605
+ mutableCtx.path = url.pathname;
568
606
  return ctx;
569
607
  }
570
608
  releaseContext(ctx) {
@@ -582,6 +620,8 @@ class QHTTPX {
582
620
  mutableCtx.serializer = null;
583
621
  mutableCtx.cookies = null;
584
622
  mutableCtx.state = null;
623
+ mutableCtx.path = '';
624
+ mutableCtx.error = undefined;
585
625
  // render method is static per context instance creation (closure over options),
586
626
  // but good to keep it consistent.
587
627
  // Wait, 'render' is defined in 'createContext' and depends on 'this.options'.
@@ -851,23 +891,29 @@ class QHTTPX {
851
891
  }
852
892
  }
853
893
  }
854
- async handleError(err, ctx) {
894
+ handleError(err, ctx) {
855
895
  const res = ctx.res;
856
896
  if (res.writableEnded) {
857
897
  return;
858
898
  }
859
899
  if (this.errorHandler) {
860
900
  try {
861
- const result = this.errorHandler(err, ctx);
901
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
902
+ const errorContext = ctx;
903
+ errorContext.error = err;
904
+ const result = this.errorHandler(errorContext);
862
905
  if (result && typeof result.then === 'function') {
863
- await result;
906
+ return result.then(() => {
907
+ // Ensure response is sent if handler didn't
908
+ });
864
909
  }
865
910
  if (res.writableEnded) {
866
911
  return;
867
912
  }
868
913
  }
869
- catch {
914
+ catch (handlerErr) {
870
915
  // Fall through to default error handling below
916
+ console.error('Error in error handler:', handlerErr);
871
917
  }
872
918
  }
873
919
  if (res.writableEnded) {
@@ -0,0 +1,15 @@
1
+ import { Readable } from 'stream';
2
+ import { QHTTPXContext } from './types';
3
+ export type SseOptions = {
4
+ retryMs?: number;
5
+ };
6
+ export type SseStream = {
7
+ send: (data: unknown, event?: string) => void;
8
+ close: () => void;
9
+ };
10
+ export declare function createSseStream(ctx: QHTTPXContext, options?: SseOptions): SseStream;
11
+ export type StreamOptions = {
12
+ contentType?: string;
13
+ status?: number;
14
+ };
15
+ export declare function sendStream(ctx: QHTTPXContext, stream: Readable, options?: StreamOptions): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { Scheduler } from './scheduler';
2
+ export type QHTTPXTaskHandler = (payload: unknown) => void | Promise<void>;
3
+ export type QHTTPXTaskOptions = {
4
+ maxRetries?: number;
5
+ backoffMs?: number;
6
+ };
7
+ export type TaskMetrics = {
8
+ registeredTasks: number;
9
+ totalEnqueued: number;
10
+ totalCompleted: number;
11
+ totalFailed: number;
12
+ totalOverloaded: number;
13
+ totalRetried: number;
14
+ };
15
+ export declare class TaskEngine {
16
+ private readonly scheduler;
17
+ private readonly tasks;
18
+ private registeredTasksCount;
19
+ private totalEnqueued;
20
+ private totalCompleted;
21
+ private totalFailed;
22
+ private totalOverloaded;
23
+ private totalRetried;
24
+ constructor(scheduler: Scheduler);
25
+ register(name: string, handler: QHTTPXTaskHandler, options?: QHTTPXTaskOptions): void;
26
+ enqueue(name: string, payload: unknown): Promise<void>;
27
+ getMetrics(): TaskMetrics;
28
+ private executeWithRetry;
29
+ }
@@ -0,0 +1,135 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { URL } from 'url';
3
+ import type { BufferPool, BufferPoolConfig } from './buffer-pool';
4
+ import type { DatabaseManager } from '../database/manager';
5
+ import type { RouteSchema, Validator } from '../validation/types';
6
+ import type { ViewEngine } from '../views/types';
7
+ import type { RequestFusionOptions } from './fusion';
8
+ export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
9
+ export declare enum RoutePriority {
10
+ CRITICAL = "critical",
11
+ STANDARD = "standard",
12
+ BEST_EFFORT = "best-effort"
13
+ }
14
+ export type RouteOptions = {
15
+ priority?: RoutePriority;
16
+ };
17
+ export declare class HttpError extends Error {
18
+ status: number;
19
+ code?: string;
20
+ details?: unknown;
21
+ constructor(status: number, message?: string, options?: {
22
+ code?: string;
23
+ details?: unknown;
24
+ });
25
+ }
26
+ export type CookieOptions = {
27
+ domain?: string;
28
+ path?: string;
29
+ expires?: Date;
30
+ maxAge?: number;
31
+ httpOnly?: boolean;
32
+ secure?: boolean;
33
+ sameSite?: 'lax' | 'strict' | 'none';
34
+ };
35
+ export type QHTTPXFile = {
36
+ filename: string;
37
+ encoding: string;
38
+ mimeType: string;
39
+ data: Buffer;
40
+ size: number;
41
+ };
42
+ export type QHTTPXContext = {
43
+ readonly req: IncomingMessage;
44
+ readonly res: ServerResponse;
45
+ readonly url: URL;
46
+ readonly params: Record<string, string>;
47
+ readonly query: Record<string, string | string[]>;
48
+ body: unknown;
49
+ files?: Record<string, QHTTPXFile | QHTTPXFile[]>;
50
+ readonly cookies: Record<string, string>;
51
+ state: Record<string, any>;
52
+ readonly bufferPool: BufferPool;
53
+ requestId: string | undefined;
54
+ requestStart: number | undefined;
55
+ serializer?: (value: unknown) => string;
56
+ readonly json: (body: unknown, status?: number) => void;
57
+ readonly send: (body: string | Buffer, status?: number) => void;
58
+ readonly html: (html: string, status?: number) => void;
59
+ readonly redirect: (url: string, status?: number) => void;
60
+ readonly setCookie: (name: string, value: string, options?: CookieOptions) => void;
61
+ readonly db?: DatabaseManager;
62
+ readonly call?: (op: string, params: any) => Promise<any>;
63
+ readonly render: (view: string, locals?: Record<string, any>) => Promise<void>;
64
+ readonly validate: <T = unknown>(schema: unknown, data?: unknown) => Promise<T>;
65
+ disableAutoEnd?: boolean;
66
+ readonly path: string;
67
+ readonly next?: () => Promise<void>;
68
+ };
69
+ export type QHTTPXOpHandler = (params: any, ctx: QHTTPXContext) => any | Promise<any>;
70
+ export type QHTTPXHandler = (ctx: QHTTPXContext) => void | Promise<void>;
71
+ export type QHTTPXRouteOptions = {
72
+ schema?: RouteSchema | Record<string, any>;
73
+ handler: QHTTPXHandler;
74
+ priority?: RoutePriority;
75
+ };
76
+ export type QHTTPXRouteConfig = Omit<QHTTPXRouteOptions, 'handler'>;
77
+ export type QHTTPXMiddleware = (ctx: QHTTPXContext, next: () => Promise<void>) => void | Promise<void>;
78
+ export type QHTTPXErrorContext = QHTTPXContext & {
79
+ error: unknown;
80
+ };
81
+ export type QHTTPXErrorHandler = (ctx: QHTTPXErrorContext) => void | Promise<void>;
82
+ export type QHTTPXNotFoundHandler = (ctx: QHTTPXContext) => void | Promise<void>;
83
+ export type QHTTPXMethodNotAllowedHandler = (ctx: QHTTPXContext, allowedMethods: HTTPMethod[]) => void | Promise<void>;
84
+ export type QHTTPXTraceEvent = {
85
+ type: 'request_start';
86
+ method: string;
87
+ path: string;
88
+ requestId?: string;
89
+ } | {
90
+ type: 'request_end';
91
+ method: string;
92
+ path: string;
93
+ statusCode: number;
94
+ durationMs: number;
95
+ requestId?: string;
96
+ };
97
+ export type QHTTPXTracer = (event: QHTTPXTraceEvent) => void | Promise<void>;
98
+ export type QHTTPXTaskHandler = (payload: unknown) => void | Promise<void>;
99
+ export type QHTTPXTaskOptions = {
100
+ maxRetries?: number;
101
+ backoffMs?: number;
102
+ };
103
+ export type PerformanceMode = 'balanced' | 'ultra';
104
+ export type QHTTPXOptions = {
105
+ name?: string;
106
+ workers?: 'auto' | number;
107
+ maxConcurrency?: number;
108
+ requestTimeoutMs?: number;
109
+ maxMemoryBytes?: number;
110
+ maxBodyBytes?: number;
111
+ keepAliveTimeoutMs?: number;
112
+ headersTimeoutMs?: number;
113
+ metricsEnabled?: boolean;
114
+ jsonSerializer?: (value: unknown) => string | Buffer;
115
+ bufferPoolConfig?: BufferPoolConfig;
116
+ errorHandler?: QHTTPXErrorHandler;
117
+ notFoundHandler?: QHTTPXNotFoundHandler;
118
+ methodNotAllowedHandler?: QHTTPXMethodNotAllowedHandler;
119
+ tracer?: QHTTPXTracer;
120
+ performanceMode?: PerformanceMode;
121
+ database?: DatabaseManager;
122
+ enableBatching?: boolean | {
123
+ endpoint: string;
124
+ };
125
+ validator?: Validator;
126
+ enableRequestFusion?: boolean | RequestFusionOptions;
127
+ viewEngine?: ViewEngine;
128
+ viewsPath?: string;
129
+ };
130
+ export type QHTTPXPlugin<Options = any> = (app: any, // We use 'any' here to avoid circular dependency with QHTTPX class, or we could use an interface
131
+ options: Options) => void | Promise<void>;
132
+ export type QHTTPXPluginOptions = {
133
+ prefix?: string;
134
+ [key: string]: any;
135
+ };
@@ -0,0 +1,25 @@
1
+ import { IncomingMessage } from 'http';
2
+ import { Duplex } from 'stream';
3
+ import { WebSocket } from 'ws';
4
+ export interface QHTTPXWebSocket extends WebSocket {
5
+ join(room: string): void;
6
+ leave(room: string): void;
7
+ id: string;
8
+ }
9
+ export type WSHandler = (ws: QHTTPXWebSocket, req: IncomingMessage) => void;
10
+ export declare class WebSocketManager {
11
+ private readonly requestIdGenerator;
12
+ private readonly wss;
13
+ private readonly handlers;
14
+ private readonly rooms;
15
+ constructor(requestIdGenerator: () => string);
16
+ register(path: string, handler: WSHandler): void;
17
+ handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): Promise<void>;
18
+ private join;
19
+ private leave;
20
+ private leaveAll;
21
+ to(room: string): {
22
+ emit: (data: unknown) => void;
23
+ };
24
+ broadcast(data: unknown): void;
25
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Lock-free (or lock-minimal) work queue for per-worker task distribution.
3
+ * Uses a simple ring buffer for high throughput.
4
+ */
5
+ export type WorkItem<T> = {
6
+ id: number;
7
+ task: T;
8
+ priority: number;
9
+ };
10
+ export declare class WorkerQueue<T> {
11
+ private readonly capacity;
12
+ private readonly buffer;
13
+ private writeIndex;
14
+ private readIndex;
15
+ private size;
16
+ constructor(capacity?: number);
17
+ /**
18
+ * Enqueue a work item. Returns true if successful, false if queue is full.
19
+ */
20
+ enqueue(item: WorkItem<T>): boolean;
21
+ /**
22
+ * Dequeue a work item. Returns undefined if queue is empty.
23
+ */
24
+ dequeue(): WorkItem<T> | undefined;
25
+ /**
26
+ * Peek at the next item without removing it.
27
+ */
28
+ peek(): WorkItem<T> | undefined;
29
+ /**
30
+ * Check if the queue is empty.
31
+ */
32
+ isEmpty(): boolean;
33
+ /**
34
+ * Get the current size of the queue.
35
+ */
36
+ getSize(): number;
37
+ /**
38
+ * Get the capacity of the queue.
39
+ */
40
+ getCapacity(): number;
41
+ }
@@ -0,0 +1,21 @@
1
+ import { DatabaseAdapter, DatabaseConfig } from '../types';
2
+ export declare class MemoryAdapter implements DatabaseAdapter {
3
+ private config;
4
+ private connected;
5
+ private collections;
6
+ constructor(config: DatabaseConfig);
7
+ connect(): Promise<void>;
8
+ disconnect(): Promise<void>;
9
+ isConnected(): boolean;
10
+ /**
11
+ * Execute a query against the in-memory database
12
+ * @param query Object specifying collection, action, and parameters
13
+ * @example
14
+ * // Find
15
+ * query({ collection: 'users', action: 'find', filter: { id: 1 } })
16
+ * // Insert
17
+ * query({ collection: 'users', action: 'insert', data: { name: 'Alice' } })
18
+ */
19
+ query<T = any>(query: any): Promise<T>;
20
+ private matches;
21
+ }
@@ -0,0 +1,11 @@
1
+ import { DatabaseAdapter, DatabaseConfig } from '../types';
2
+ export declare class MongoAdapter implements DatabaseAdapter {
3
+ private client;
4
+ private db;
5
+ private config;
6
+ constructor(config: DatabaseConfig);
7
+ connect(): Promise<void>;
8
+ disconnect(): Promise<void>;
9
+ query<T = any>(query: string | object, params?: any[]): Promise<T>;
10
+ isConnected(): boolean;
11
+ }
@@ -0,0 +1,10 @@
1
+ import { DatabaseAdapter, DatabaseConfig } from '../types';
2
+ export declare class PostgresAdapter implements DatabaseAdapter {
3
+ private pool;
4
+ private config;
5
+ constructor(config: DatabaseConfig);
6
+ connect(): Promise<void>;
7
+ disconnect(): Promise<void>;
8
+ query<T = any>(sql: string, params?: any[]): Promise<T>;
9
+ isConnected(): boolean;
10
+ }
@@ -0,0 +1,10 @@
1
+ import { DatabaseAdapter, DatabaseConfig } from '../types';
2
+ export declare class SQLiteAdapter implements DatabaseAdapter {
3
+ private db;
4
+ private config;
5
+ constructor(config: DatabaseConfig);
6
+ connect(): Promise<void>;
7
+ disconnect(): Promise<void>;
8
+ query<T = any>(sql: string, params?: any[]): Promise<T>;
9
+ isConnected(): boolean;
10
+ }
@@ -0,0 +1,14 @@
1
+ import { DatabaseAdapter } from './types';
2
+ export declare class QueryCoalescer<T = unknown> {
3
+ private pending;
4
+ private timeout;
5
+ private readonly adapter;
6
+ constructor(adapter: DatabaseAdapter);
7
+ /**
8
+ * Intercepts a query and attempts to coalesce it with others.
9
+ * Only supports simple queries of the form "SELECT ... WHERE col = ?" for now.
10
+ */
11
+ query(query: string, params?: unknown[]): Promise<T>;
12
+ private scheduleFlush;
13
+ private flush;
14
+ }