qhttpx 1.8.2 → 1.8.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 +10 -0
- package/assets/logo.svg +24 -10
- 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 +86 -0
- package/dist/src/core/server.js +122 -94
- 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 +134 -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/package.json +1 -1
- package/src/core/server.ts +130 -91
- package/src/core/types.ts +14 -4
- package/src/index.ts +8 -0
- package/tests/dx.test.ts +109 -57
- package/tsconfig.json +1 -0
package/dist/src/core/server.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
|
@@ -450,95 +466,98 @@ class QHTTPX {
|
|
|
450
466
|
requestId: '',
|
|
451
467
|
requestStart: 0,
|
|
452
468
|
serializer: null,
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
},
|
|
469
|
+
path: '',
|
|
470
|
+
error: undefined,
|
|
512
471
|
db: this.options.database,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
472
|
+
};
|
|
473
|
+
// Helper to get response object from closure-captured ctx
|
|
474
|
+
// We use arrow functions to ensure they don't depend on 'this' context at call site
|
|
475
|
+
// enabling destructuring like: ({ json }) => json(...)
|
|
476
|
+
ctx.json = (payload, status = 200) => {
|
|
477
|
+
const res = ctx.res;
|
|
478
|
+
if (!res.headersSent) {
|
|
479
|
+
res.statusCode = status;
|
|
480
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
481
|
+
}
|
|
482
|
+
let body;
|
|
483
|
+
if (ctx.serializer) {
|
|
484
|
+
body = ctx.serializer(payload);
|
|
485
|
+
}
|
|
486
|
+
else if (useFastStringify) {
|
|
487
|
+
body = (0, serializer_1.fastJsonStringify)(payload);
|
|
488
|
+
}
|
|
489
|
+
else if (jsonSerializer) {
|
|
490
|
+
body = jsonSerializer(payload);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
body = JSON.stringify(payload);
|
|
494
|
+
}
|
|
495
|
+
res.end(body);
|
|
496
|
+
};
|
|
497
|
+
ctx.send = (payload, status = 200) => {
|
|
498
|
+
const res = ctx.res;
|
|
499
|
+
if (!res.headersSent) {
|
|
500
|
+
res.statusCode = status;
|
|
501
|
+
}
|
|
502
|
+
res.end(payload);
|
|
503
|
+
};
|
|
504
|
+
ctx.html = (payload, status = 200) => {
|
|
505
|
+
const res = ctx.res;
|
|
506
|
+
if (!res.headersSent) {
|
|
507
|
+
res.statusCode = status;
|
|
508
|
+
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
509
|
+
}
|
|
510
|
+
res.end(payload);
|
|
511
|
+
};
|
|
512
|
+
ctx.redirect = (url, status = 302) => {
|
|
513
|
+
const res = ctx.res;
|
|
514
|
+
if (!res.headersSent) {
|
|
515
|
+
res.statusCode = status;
|
|
516
|
+
res.setHeader('Location', url);
|
|
517
|
+
}
|
|
518
|
+
res.end();
|
|
519
|
+
};
|
|
520
|
+
ctx.setCookie = (name, value, options) => {
|
|
521
|
+
const res = ctx.res;
|
|
522
|
+
const serialized = (0, cookies_1.serializeCookie)(name, value, options);
|
|
523
|
+
let existing = res.getHeader('Set-Cookie');
|
|
524
|
+
if (Array.isArray(existing)) {
|
|
525
|
+
existing.push(serialized);
|
|
526
|
+
res.setHeader('Set-Cookie', existing);
|
|
527
|
+
}
|
|
528
|
+
else if (existing) {
|
|
529
|
+
res.setHeader('Set-Cookie', [existing, serialized]);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
res.setHeader('Set-Cookie', serialized);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
536
|
+
ctx.render = async (view, locals) => {
|
|
537
|
+
const engine = this.options.viewEngine;
|
|
538
|
+
if (!engine) {
|
|
539
|
+
throw new Error('No view engine registered');
|
|
540
|
+
}
|
|
541
|
+
const viewsPath = this.options.viewsPath || process.cwd();
|
|
542
|
+
const fullPath = path_1.default.resolve(viewsPath, view);
|
|
543
|
+
const html = await engine.render(fullPath, locals || {});
|
|
544
|
+
const res = ctx.res;
|
|
545
|
+
if (!res.headersSent) {
|
|
546
|
+
res.statusCode = 200;
|
|
547
|
+
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
548
|
+
}
|
|
549
|
+
res.end(html);
|
|
550
|
+
};
|
|
551
|
+
ctx.validate = async (schema, data) => {
|
|
552
|
+
const target = data ?? ctx.body;
|
|
553
|
+
const result = await this.validator.validate(schema, target);
|
|
554
|
+
if (result.success) {
|
|
555
|
+
return result.data;
|
|
556
|
+
}
|
|
557
|
+
throw new types_1.HttpError(400, 'Validation Error', {
|
|
558
|
+
code: 'VALIDATION_ERROR',
|
|
559
|
+
details: result.error,
|
|
560
|
+
});
|
|
542
561
|
};
|
|
543
562
|
return ctx;
|
|
544
563
|
}
|
|
@@ -565,6 +584,7 @@ class QHTTPX {
|
|
|
565
584
|
}
|
|
566
585
|
mutableCtx.state = {};
|
|
567
586
|
mutableCtx.disableAutoEnd = false;
|
|
587
|
+
mutableCtx.path = url.pathname;
|
|
568
588
|
return ctx;
|
|
569
589
|
}
|
|
570
590
|
releaseContext(ctx) {
|
|
@@ -582,6 +602,8 @@ class QHTTPX {
|
|
|
582
602
|
mutableCtx.serializer = null;
|
|
583
603
|
mutableCtx.cookies = null;
|
|
584
604
|
mutableCtx.state = null;
|
|
605
|
+
mutableCtx.path = '';
|
|
606
|
+
mutableCtx.error = undefined;
|
|
585
607
|
// render method is static per context instance creation (closure over options),
|
|
586
608
|
// but good to keep it consistent.
|
|
587
609
|
// Wait, 'render' is defined in 'createContext' and depends on 'this.options'.
|
|
@@ -851,23 +873,29 @@ class QHTTPX {
|
|
|
851
873
|
}
|
|
852
874
|
}
|
|
853
875
|
}
|
|
854
|
-
|
|
876
|
+
handleError(err, ctx) {
|
|
855
877
|
const res = ctx.res;
|
|
856
878
|
if (res.writableEnded) {
|
|
857
879
|
return;
|
|
858
880
|
}
|
|
859
881
|
if (this.errorHandler) {
|
|
860
882
|
try {
|
|
861
|
-
|
|
883
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
884
|
+
const errorContext = ctx;
|
|
885
|
+
errorContext.error = err;
|
|
886
|
+
const result = this.errorHandler(errorContext);
|
|
862
887
|
if (result && typeof result.then === 'function') {
|
|
863
|
-
|
|
888
|
+
return result.then(() => {
|
|
889
|
+
// Ensure response is sent if handler didn't
|
|
890
|
+
});
|
|
864
891
|
}
|
|
865
892
|
if (res.writableEnded) {
|
|
866
893
|
return;
|
|
867
894
|
}
|
|
868
895
|
}
|
|
869
|
-
catch {
|
|
896
|
+
catch (handlerErr) {
|
|
870
897
|
// Fall through to default error handling below
|
|
898
|
+
console.error('Error in error handler:', handlerErr);
|
|
871
899
|
}
|
|
872
900
|
}
|
|
873
901
|
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,134 @@
|
|
|
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 QHTTPXMiddleware = (ctx: QHTTPXContext, next: () => Promise<void>) => void | Promise<void>;
|
|
77
|
+
export type QHTTPXErrorContext = QHTTPXContext & {
|
|
78
|
+
error: unknown;
|
|
79
|
+
};
|
|
80
|
+
export type QHTTPXErrorHandler = (ctx: QHTTPXErrorContext) => void | Promise<void>;
|
|
81
|
+
export type QHTTPXNotFoundHandler = (ctx: QHTTPXContext) => void | Promise<void>;
|
|
82
|
+
export type QHTTPXMethodNotAllowedHandler = (ctx: QHTTPXContext, allowedMethods: HTTPMethod[]) => void | Promise<void>;
|
|
83
|
+
export type QHTTPXTraceEvent = {
|
|
84
|
+
type: 'request_start';
|
|
85
|
+
method: string;
|
|
86
|
+
path: string;
|
|
87
|
+
requestId?: string;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'request_end';
|
|
90
|
+
method: string;
|
|
91
|
+
path: string;
|
|
92
|
+
statusCode: number;
|
|
93
|
+
durationMs: number;
|
|
94
|
+
requestId?: string;
|
|
95
|
+
};
|
|
96
|
+
export type QHTTPXTracer = (event: QHTTPXTraceEvent) => void | Promise<void>;
|
|
97
|
+
export type QHTTPXTaskHandler = (payload: unknown) => void | Promise<void>;
|
|
98
|
+
export type QHTTPXTaskOptions = {
|
|
99
|
+
maxRetries?: number;
|
|
100
|
+
backoffMs?: number;
|
|
101
|
+
};
|
|
102
|
+
export type PerformanceMode = 'balanced' | 'ultra';
|
|
103
|
+
export type QHTTPXOptions = {
|
|
104
|
+
name?: string;
|
|
105
|
+
workers?: 'auto' | number;
|
|
106
|
+
maxConcurrency?: number;
|
|
107
|
+
requestTimeoutMs?: number;
|
|
108
|
+
maxMemoryBytes?: number;
|
|
109
|
+
maxBodyBytes?: number;
|
|
110
|
+
keepAliveTimeoutMs?: number;
|
|
111
|
+
headersTimeoutMs?: number;
|
|
112
|
+
metricsEnabled?: boolean;
|
|
113
|
+
jsonSerializer?: (value: unknown) => string | Buffer;
|
|
114
|
+
bufferPoolConfig?: BufferPoolConfig;
|
|
115
|
+
errorHandler?: QHTTPXErrorHandler;
|
|
116
|
+
notFoundHandler?: QHTTPXNotFoundHandler;
|
|
117
|
+
methodNotAllowedHandler?: QHTTPXMethodNotAllowedHandler;
|
|
118
|
+
tracer?: QHTTPXTracer;
|
|
119
|
+
performanceMode?: PerformanceMode;
|
|
120
|
+
database?: DatabaseManager;
|
|
121
|
+
enableBatching?: boolean | {
|
|
122
|
+
endpoint: string;
|
|
123
|
+
};
|
|
124
|
+
validator?: Validator;
|
|
125
|
+
enableRequestFusion?: boolean | RequestFusionOptions;
|
|
126
|
+
viewEngine?: ViewEngine;
|
|
127
|
+
viewsPath?: string;
|
|
128
|
+
};
|
|
129
|
+
export type QHTTPXPlugin<Options = any> = (app: any, // We use 'any' here to avoid circular dependency with QHTTPX class, or we could use an interface
|
|
130
|
+
options: Options) => void | Promise<void>;
|
|
131
|
+
export type QHTTPXPluginOptions = {
|
|
132
|
+
prefix?: string;
|
|
133
|
+
[key: string]: any;
|
|
134
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DatabaseAdapter, DatabaseConfig, DatabaseEngineOptions } from './types';
|
|
2
|
+
export type AdapterConstructor = new (config: DatabaseConfig) => DatabaseAdapter;
|
|
3
|
+
export declare class DatabaseManager {
|
|
4
|
+
private config;
|
|
5
|
+
private static adapterRegistry;
|
|
6
|
+
private connections;
|
|
7
|
+
constructor(config: DatabaseEngineOptions);
|
|
8
|
+
/**
|
|
9
|
+
* Manually register an already initialized adapter instance
|
|
10
|
+
* @param name The connection name
|
|
11
|
+
* @param adapter The initialized adapter instance
|
|
12
|
+
*/
|
|
13
|
+
registerConnection(name: string, adapter: DatabaseAdapter): void;
|
|
14
|
+
/**
|
|
15
|
+
* Register a new database adapter type
|
|
16
|
+
* @param type The type identifier (e.g., 'postgres', 'mysql', 'mongo')
|
|
17
|
+
* @param adapter The adapter class
|
|
18
|
+
*/
|
|
19
|
+
static registerAdapter(type: string, adapter: AdapterConstructor): void;
|
|
20
|
+
/**
|
|
21
|
+
* Connect to a specific database or the default one
|
|
22
|
+
* @param name Connection name from config
|
|
23
|
+
*/
|
|
24
|
+
connect(name?: string): Promise<DatabaseAdapter>;
|
|
25
|
+
/**
|
|
26
|
+
* Disconnect a specific connection or all connections
|
|
27
|
+
* @param name Connection name (optional). If not provided, disconnects all.
|
|
28
|
+
*/
|
|
29
|
+
disconnect(name?: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get an active connection
|
|
32
|
+
* @param name Connection name
|
|
33
|
+
*/
|
|
34
|
+
get(name?: string): DatabaseAdapter;
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DatabaseConfig {
|
|
2
|
+
type: string;
|
|
3
|
+
url?: string;
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
username?: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
database?: string;
|
|
9
|
+
options?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export interface DatabaseAdapter {
|
|
12
|
+
connect(): Promise<void>;
|
|
13
|
+
disconnect(): Promise<void>;
|
|
14
|
+
query<T = any>(query: string | object, params?: any[]): Promise<T>;
|
|
15
|
+
isConnected(): boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface DatabaseEngineOptions {
|
|
18
|
+
default?: string;
|
|
19
|
+
connections: Record<string, DatabaseConfig>;
|
|
20
|
+
}
|