qhttpx 1.8.5 → 1.8.11

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 (161) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +72 -52
  3. package/binding.gyp +18 -0
  4. package/dist/examples/api-server.js +29 -8
  5. package/dist/examples/basic.d.ts +1 -0
  6. package/dist/examples/basic.js +10 -0
  7. package/dist/examples/compression.d.ts +1 -0
  8. package/dist/examples/compression.js +17 -0
  9. package/dist/examples/cors.d.ts +1 -0
  10. package/dist/examples/cors.js +19 -0
  11. package/dist/examples/errors.d.ts +1 -0
  12. package/dist/examples/errors.js +25 -0
  13. package/dist/examples/file-upload.d.ts +1 -0
  14. package/dist/examples/file-upload.js +24 -0
  15. package/dist/examples/fusion.d.ts +1 -0
  16. package/dist/examples/fusion.js +21 -0
  17. package/dist/examples/rate-limiting.d.ts +1 -0
  18. package/dist/examples/rate-limiting.js +17 -0
  19. package/dist/examples/validation.d.ts +1 -0
  20. package/dist/examples/validation.js +23 -0
  21. package/dist/examples/websockets.d.ts +1 -0
  22. package/dist/examples/websockets.js +20 -0
  23. package/dist/package.json +11 -1
  24. package/dist/src/benchmarks/simple-json.js +6 -4
  25. package/dist/src/cli/index.js +33 -11
  26. package/dist/src/core/errors.d.ts +34 -0
  27. package/dist/src/core/errors.js +70 -0
  28. package/dist/src/core/native-adapter.d.ts +11 -0
  29. package/dist/src/core/native-adapter.js +211 -0
  30. package/dist/src/core/server.d.ts +52 -4
  31. package/dist/src/core/server.js +389 -261
  32. package/dist/src/core/types.d.ts +37 -0
  33. package/dist/src/index.d.ts +6 -1
  34. package/dist/src/index.js +19 -3
  35. package/dist/src/middleware/compression.d.ts +1 -5
  36. package/dist/src/middleware/cors.d.ts +1 -10
  37. package/dist/src/middleware/presets.d.ts +4 -1
  38. package/dist/src/middleware/presets.js +22 -3
  39. package/dist/src/middleware/rate-limit.d.ts +1 -19
  40. package/dist/src/middleware/rate-limit.js +6 -0
  41. package/dist/src/middleware/security.d.ts +1 -2
  42. package/dist/src/native/index.d.ts +29 -0
  43. package/dist/src/native/index.js +64 -0
  44. package/dist/src/router/radix-tree.d.ts +2 -0
  45. package/dist/src/router/radix-tree.js +54 -4
  46. package/dist/src/router/router.d.ts +1 -0
  47. package/dist/src/router/router.js +42 -2
  48. package/dist/tests/native-adapter.test.d.ts +1 -0
  49. package/dist/tests/native-adapter.test.js +71 -0
  50. package/dist/tests/resources.test.js +3 -0
  51. package/dist/tests/security.test.js +2 -2
  52. package/docs/AEGIS.md +34 -9
  53. package/docs/BENCHMARKS.md +8 -7
  54. package/docs/ERRORS.md +112 -0
  55. package/docs/FUSION.md +68 -0
  56. package/docs/MIDDLEWARE.md +65 -0
  57. package/docs/ROUTING.md +70 -0
  58. package/docs/STATIC.md +61 -0
  59. package/docs/WEBSOCKETS.md +76 -0
  60. package/package.json +11 -1
  61. package/src/native/README.md +31 -0
  62. package/src/native/addon.cc +8 -0
  63. package/src/native/index.ts +78 -0
  64. package/src/native/picohttpparser.c +608 -0
  65. package/src/native/picohttpparser.h +71 -0
  66. package/src/native/server.cc +262 -0
  67. package/src/native/server.h +30 -0
  68. package/.eslintrc.json +0 -22
  69. package/.github/workflows/ci.yml +0 -32
  70. package/.github/workflows/npm-publish.yml +0 -37
  71. package/.github/workflows/release.yml +0 -21
  72. package/.prettierrc +0 -7
  73. package/assets/logo.svg +0 -25
  74. package/eslint.config.cjs +0 -26
  75. package/examples/api-server.ts +0 -62
  76. package/src/benchmarks/quantam-users.ts +0 -70
  77. package/src/benchmarks/simple-json.ts +0 -71
  78. package/src/benchmarks/ultra-mode.ts +0 -127
  79. package/src/cli/index.ts +0 -214
  80. package/src/client/index.ts +0 -93
  81. package/src/core/batch.ts +0 -110
  82. package/src/core/body-parser.ts +0 -151
  83. package/src/core/buffer-pool.ts +0 -96
  84. package/src/core/config.ts +0 -60
  85. package/src/core/fusion.ts +0 -210
  86. package/src/core/logger.ts +0 -70
  87. package/src/core/metrics.ts +0 -166
  88. package/src/core/resources.ts +0 -38
  89. package/src/core/scheduler.ts +0 -126
  90. package/src/core/scope.ts +0 -87
  91. package/src/core/serializer.ts +0 -41
  92. package/src/core/server.ts +0 -1234
  93. package/src/core/stream.ts +0 -111
  94. package/src/core/tasks.ts +0 -138
  95. package/src/core/types.ts +0 -192
  96. package/src/core/websocket.ts +0 -112
  97. package/src/core/worker-queue.ts +0 -90
  98. package/src/database/adapters/memory.ts +0 -99
  99. package/src/database/adapters/mongo.ts +0 -116
  100. package/src/database/adapters/postgres.ts +0 -86
  101. package/src/database/adapters/sqlite.ts +0 -44
  102. package/src/database/coalescer.ts +0 -153
  103. package/src/database/manager.ts +0 -97
  104. package/src/database/types.ts +0 -24
  105. package/src/index.ts +0 -58
  106. package/src/middleware/compression.ts +0 -147
  107. package/src/middleware/cors.ts +0 -98
  108. package/src/middleware/presets.ts +0 -50
  109. package/src/middleware/rate-limit.ts +0 -106
  110. package/src/middleware/security.ts +0 -109
  111. package/src/middleware/static.ts +0 -216
  112. package/src/openapi/generator.ts +0 -167
  113. package/src/router/radix-router.ts +0 -119
  114. package/src/router/radix-tree.ts +0 -106
  115. package/src/router/router.ts +0 -190
  116. package/src/testing/index.ts +0 -104
  117. package/src/utils/cookies.ts +0 -67
  118. package/src/utils/logger.ts +0 -59
  119. package/src/utils/signals.ts +0 -45
  120. package/src/utils/sse.ts +0 -41
  121. package/src/validation/index.ts +0 -3
  122. package/src/validation/simple.ts +0 -93
  123. package/src/validation/types.ts +0 -38
  124. package/src/validation/zod.ts +0 -14
  125. package/src/views/index.ts +0 -1
  126. package/src/views/types.ts +0 -4
  127. package/tests/adapters.test.ts +0 -120
  128. package/tests/batch.test.ts +0 -139
  129. package/tests/body-parser.test.ts +0 -83
  130. package/tests/compression-sse.test.ts +0 -98
  131. package/tests/cookies.test.ts +0 -74
  132. package/tests/cors.test.ts +0 -79
  133. package/tests/database.test.ts +0 -90
  134. package/tests/dx.test.ts +0 -130
  135. package/tests/ecosystem.test.ts +0 -156
  136. package/tests/features.test.ts +0 -51
  137. package/tests/fusion.test.ts +0 -121
  138. package/tests/http-basic.test.ts +0 -161
  139. package/tests/logger.test.ts +0 -48
  140. package/tests/middleware.test.ts +0 -137
  141. package/tests/observability.test.ts +0 -91
  142. package/tests/openapi.test.ts +0 -74
  143. package/tests/plugin.test.ts +0 -85
  144. package/tests/plugins.test.ts +0 -93
  145. package/tests/rate-limit.test.ts +0 -97
  146. package/tests/resources.test.ts +0 -64
  147. package/tests/scheduler.test.ts +0 -71
  148. package/tests/schema-routes.test.ts +0 -89
  149. package/tests/security.test.ts +0 -128
  150. package/tests/server-db.test.ts +0 -72
  151. package/tests/smoke.test.ts +0 -9
  152. package/tests/sqlite-fusion.test.ts +0 -106
  153. package/tests/static.test.ts +0 -111
  154. package/tests/stream.test.ts +0 -58
  155. package/tests/task-metrics.test.ts +0 -78
  156. package/tests/tasks.test.ts +0 -90
  157. package/tests/testing.test.ts +0 -53
  158. package/tests/validation.test.ts +0 -126
  159. package/tests/websocket.test.ts +0 -132
  160. package/tsconfig.json +0 -17
  161. package/vitest.config.ts +0 -9
@@ -44,6 +44,8 @@ export type QHTTPXContext = {
44
44
  readonly res: ServerResponse;
45
45
  readonly headers: IncomingHttpHeaders;
46
46
  readonly url: URL;
47
+ readonly method: HTTPMethod;
48
+ readonly ip: string;
47
49
  readonly params: Record<string, string>;
48
50
  readonly query: Record<string, string | string[]>;
49
51
  body: unknown;
@@ -102,6 +104,25 @@ export type QHTTPXTaskOptions = {
102
104
  backoffMs?: number;
103
105
  };
104
106
  export type PerformanceMode = 'balanced' | 'ultra';
107
+ export interface RateLimitStore {
108
+ increment(key: string, windowMs: number): Promise<{
109
+ total: number;
110
+ resetTime: number;
111
+ }>;
112
+ decrement(key: string): Promise<void>;
113
+ reset(key: string): Promise<void>;
114
+ }
115
+ export interface RateLimitOptions {
116
+ windowMs?: number;
117
+ max?: number;
118
+ message?: string | object;
119
+ statusCode?: number;
120
+ headers?: boolean;
121
+ keyGenerator?: (ctx: QHTTPXContext) => string;
122
+ skip?: (ctx: QHTTPXContext) => boolean;
123
+ store?: RateLimitStore;
124
+ trustProxy?: boolean;
125
+ }
105
126
  export type QHTTPXOptions = {
106
127
  name?: string;
107
128
  workers?: 'auto' | number;
@@ -127,6 +148,22 @@ export type QHTTPXOptions = {
127
148
  enableRequestFusion?: boolean | RequestFusionOptions;
128
149
  viewEngine?: ViewEngine;
129
150
  viewsPath?: string;
151
+ rateLimit?: RateLimitOptions;
152
+ cors?: CorsOptions | boolean;
153
+ compression?: CompressionOptions | boolean;
154
+ };
155
+ export type CorsOrigin = string | string[] | ((origin: string | undefined) => string | null | undefined);
156
+ export type CorsOptions = {
157
+ origin?: CorsOrigin;
158
+ methods?: string[];
159
+ allowedHeaders?: string[];
160
+ exposedHeaders?: string[];
161
+ credentials?: boolean;
162
+ maxAgeSeconds?: number;
163
+ };
164
+ export type CompressionOptions = {
165
+ threshold?: number;
166
+ level?: number;
130
167
  };
131
168
  export type QHTTPXPlugin<Options = any> = (app: any, // We use 'any' here to avoid circular dependency with QHTTPX class, or we could use an interface
132
169
  options: Options) => void | Promise<void>;
@@ -1,9 +1,12 @@
1
1
  import { QHTTPX } from './core/server';
2
2
  import type { QHTTPXOptions } from './core/types';
3
+ import { NativeAdapter } from './core/native-adapter';
3
4
  export { QHTTPX } from './core/server';
4
5
  export * from './core/types';
6
+ export * from './core/errors';
5
7
  export * from './middleware/cors';
6
- export * from './middleware/security';
8
+ export { createSecurityHeadersMiddleware, type SecurityHeadersOptions, createSecureDefaults, type SecureDefaultsOptions, } from './middleware/security';
9
+ export * from './middleware/rate-limit';
7
10
  export * from './middleware/static';
8
11
  export * from './middleware/compression';
9
12
  export * from './core/stream';
@@ -28,7 +31,9 @@ export * from './validation/types';
28
31
  export * from './validation/simple';
29
32
  export * from './openapi/generator';
30
33
  export * from './client';
34
+ export { NativeAdapter } from './core/native-adapter';
31
35
  export declare function createHttpApp(options?: QHTTPXOptions): QHTTPX;
36
+ export declare function createNativeApp(options?: QHTTPXOptions): NativeAdapter;
32
37
  /**
33
38
  * Singleton instance for quick start
34
39
  * @example
package/dist/src/index.js CHANGED
@@ -14,15 +14,21 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.app = exports.getStringifier = exports.fastJsonStringify = exports.BufferPool = exports.QHTTPX = void 0;
17
+ exports.app = exports.NativeAdapter = exports.getStringifier = exports.fastJsonStringify = exports.BufferPool = exports.createSecureDefaults = exports.createSecurityHeadersMiddleware = exports.QHTTPX = void 0;
18
18
  exports.createHttpApp = createHttpApp;
19
+ exports.createNativeApp = createNativeApp;
19
20
  const server_1 = require("./core/server");
20
21
  const presets_1 = require("./middleware/presets");
22
+ const native_adapter_1 = require("./core/native-adapter");
21
23
  var server_2 = require("./core/server");
22
24
  Object.defineProperty(exports, "QHTTPX", { enumerable: true, get: function () { return server_2.QHTTPX; } });
23
25
  __exportStar(require("./core/types"), exports);
26
+ __exportStar(require("./core/errors"), exports);
24
27
  __exportStar(require("./middleware/cors"), exports);
25
- __exportStar(require("./middleware/security"), exports);
28
+ var security_1 = require("./middleware/security");
29
+ Object.defineProperty(exports, "createSecurityHeadersMiddleware", { enumerable: true, get: function () { return security_1.createSecurityHeadersMiddleware; } });
30
+ Object.defineProperty(exports, "createSecureDefaults", { enumerable: true, get: function () { return security_1.createSecureDefaults; } });
31
+ __exportStar(require("./middleware/rate-limit"), exports);
26
32
  __exportStar(require("./middleware/static"), exports);
27
33
  __exportStar(require("./middleware/compression"), exports);
28
34
  __exportStar(require("./core/stream"), exports);
@@ -50,15 +56,25 @@ __exportStar(require("./validation/types"), exports);
50
56
  __exportStar(require("./validation/simple"), exports);
51
57
  __exportStar(require("./openapi/generator"), exports);
52
58
  __exportStar(require("./client"), exports);
59
+ var native_adapter_2 = require("./core/native-adapter");
60
+ Object.defineProperty(exports, "NativeAdapter", { enumerable: true, get: function () { return native_adapter_2.NativeAdapter; } });
53
61
  function createHttpApp(options = {}) {
54
62
  const app = new server_1.QHTTPX(options);
55
63
  // Skip middleware in ultra mode for maximum performance
56
64
  if (options.performanceMode !== 'ultra') {
57
- const middlewares = (0, presets_1.createApiPreset)();
65
+ const middlewares = (0, presets_1.createApiPreset)({
66
+ rateLimit: options.rateLimit,
67
+ cors: options.cors,
68
+ compression: options.compression,
69
+ });
58
70
  middlewares.forEach((mw) => app.use(mw));
59
71
  }
60
72
  return app;
61
73
  }
74
+ function createNativeApp(options = {}) {
75
+ const app = createHttpApp(options);
76
+ return new native_adapter_1.NativeAdapter(app);
77
+ }
62
78
  /**
63
79
  * Singleton instance for quick start
64
80
  * @example
@@ -1,6 +1,2 @@
1
- import { QHTTPXMiddleware } from '../core/types';
2
- export type CompressionOptions = {
3
- threshold?: number;
4
- level?: number;
5
- };
1
+ import { QHTTPXMiddleware, CompressionOptions } from '../core/types';
6
2
  export declare function createCompressionMiddleware(options?: CompressionOptions): QHTTPXMiddleware;
@@ -1,11 +1,2 @@
1
- import { QHTTPXMiddleware } from '../core/types';
2
- export type CorsOrigin = string | string[] | ((origin: string | undefined) => string | null | undefined);
3
- export type CorsOptions = {
4
- origin?: CorsOrigin;
5
- methods?: string[];
6
- allowedHeaders?: string[];
7
- exposedHeaders?: string[];
8
- credentials?: boolean;
9
- maxAgeSeconds?: number;
10
- };
1
+ import { QHTTPXMiddleware, CorsOptions } from '../core/types';
11
2
  export declare function createCorsMiddleware(options?: CorsOptions): QHTTPXMiddleware;
@@ -1,10 +1,13 @@
1
- import { QHTTPXMiddleware } from '../core/types';
1
+ import { QHTTPXMiddleware, RateLimitOptions, CorsOptions, CompressionOptions } from '../core/types';
2
2
  import { SecureDefaultsOptions } from './security';
3
3
  import { LoggerOptions } from '../utils/logger';
4
4
  import { StaticOptions } from './static';
5
5
  export type ApiPresetOptions = {
6
6
  security?: SecureDefaultsOptions;
7
7
  logging?: LoggerOptions | boolean;
8
+ rateLimit?: RateLimitOptions;
9
+ cors?: CorsOptions | boolean;
10
+ compression?: CompressionOptions | boolean;
8
11
  };
9
12
  export declare function createApiPreset(options?: ApiPresetOptions): QHTTPXMiddleware[];
10
13
  export type StaticAppPresetOptions = ApiPresetOptions & {
@@ -3,17 +3,36 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createApiPreset = createApiPreset;
4
4
  exports.createStaticAppPreset = createStaticAppPreset;
5
5
  const security_1 = require("./security");
6
+ const cors_1 = require("./cors");
7
+ const compression_1 = require("./compression");
6
8
  const logger_1 = require("../utils/logger");
7
9
  const static_1 = require("./static");
10
+ const rate_limit_1 = require("./rate-limit");
8
11
  function createApiPreset(options = {}) {
9
12
  const middlewares = [];
10
- // 1. Security (CORS, Headers)
11
- middlewares.push(...(0, security_1.createSecureDefaults)(options.security));
12
- // 2. Logging
13
+ // 1. CORS
14
+ // Default to true (enabled) if not specified, to match createSecureDefaults behavior
15
+ const corsOpts = options.cors ?? options.security?.cors ?? true;
16
+ if (corsOpts !== false) {
17
+ const opts = corsOpts === true ? {} : corsOpts;
18
+ middlewares.push((0, cors_1.createCorsMiddleware)(opts));
19
+ }
20
+ // 2. Security Headers
21
+ middlewares.push((0, security_1.createSecurityHeadersMiddleware)(options.security?.securityHeaders));
22
+ // 3. Compression
23
+ if (options.compression) {
24
+ const opts = options.compression === true ? {} : options.compression;
25
+ middlewares.push((0, compression_1.createCompressionMiddleware)(opts));
26
+ }
27
+ // 4. Logging
13
28
  if (options.logging !== false) {
14
29
  const loggerOptions = typeof options.logging === 'object' ? options.logging : {};
15
30
  middlewares.push((0, logger_1.createLoggerMiddleware)(loggerOptions));
16
31
  }
32
+ // 5. Rate Limit
33
+ if (options.rateLimit) {
34
+ middlewares.push((0, rate_limit_1.rateLimit)(options.rateLimit));
35
+ }
17
36
  return middlewares;
18
37
  }
19
38
  function createStaticAppPreset(options) {
@@ -1,12 +1,4 @@
1
- import type { QHTTPXContext, QHTTPXMiddleware } from '../core/types';
2
- export interface RateLimitStore {
3
- increment(key: string, windowMs: number): Promise<{
4
- total: number;
5
- resetTime: number;
6
- }>;
7
- decrement(key: string): Promise<void>;
8
- reset(key: string): Promise<void>;
9
- }
1
+ import type { QHTTPXMiddleware, RateLimitOptions, RateLimitStore } from '../core/types';
10
2
  export declare class MemoryStore implements RateLimitStore {
11
3
  private hits;
12
4
  private interval?;
@@ -19,14 +11,4 @@ export declare class MemoryStore implements RateLimitStore {
19
11
  reset(key: string): Promise<void>;
20
12
  private cleanup;
21
13
  }
22
- export interface RateLimitOptions {
23
- windowMs?: number;
24
- max?: number;
25
- message?: string | object;
26
- statusCode?: number;
27
- headers?: boolean;
28
- keyGenerator?: (ctx: QHTTPXContext) => string;
29
- skip?: (ctx: QHTTPXContext) => boolean;
30
- store?: RateLimitStore;
31
- }
32
14
  export declare const rateLimit: (options?: RateLimitOptions) => QHTTPXMiddleware;
@@ -49,6 +49,12 @@ const rateLimit = (options = {}) => {
49
49
  const headers = options.headers ?? true;
50
50
  const store = options.store ?? new MemoryStore();
51
51
  const keyGenerator = options.keyGenerator ?? ((ctx) => {
52
+ if (options.trustProxy) {
53
+ const forwarded = ctx.req.headers['x-forwarded-for'];
54
+ if (forwarded) {
55
+ return Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0].trim();
56
+ }
57
+ }
52
58
  return ctx.req.socket.remoteAddress || 'unknown';
53
59
  });
54
60
  return async (ctx, next) => {
@@ -1,5 +1,4 @@
1
- import { QHTTPXContext, QHTTPXMiddleware } from '../core/types';
2
- import { CorsOptions } from './cors';
1
+ import { QHTTPXContext, QHTTPXMiddleware, CorsOptions } from '../core/types';
3
2
  export type SecurityHeadersOptions = {
4
3
  contentSecurityPolicy?: string | null;
5
4
  referrerPolicy?: string | null;
@@ -0,0 +1,29 @@
1
+ export interface NativeServerBinding {
2
+ parse(buffer: Buffer): {
3
+ method: string;
4
+ path: string;
5
+ version: number;
6
+ headers: Record<string, string>;
7
+ bodyOffset: number;
8
+ } | null;
9
+ createResponse(statusCode: number, headers: Record<string, string>, body?: string | Buffer): Buffer;
10
+ createJSONResponse(obj: unknown): Buffer;
11
+ writeResponse(fd: number, chunks: (Buffer | string)[]): void;
12
+ setCPUAffinity(cpuId: number): boolean;
13
+ }
14
+ export declare class NativeServer {
15
+ private binding;
16
+ constructor();
17
+ get isAvailable(): boolean;
18
+ parse(buffer: Buffer): {
19
+ method: string;
20
+ path: string;
21
+ version: number;
22
+ headers: Record<string, string>;
23
+ bodyOffset: number;
24
+ } | null;
25
+ createResponse(statusCode: number, headers: Record<string, string>, body?: string | Buffer): Buffer<ArrayBufferLike>;
26
+ createJSONResponse(obj: unknown): Buffer<ArrayBufferLike>;
27
+ writeResponse(fd: number, chunks: (Buffer | string)[]): void;
28
+ setCPUAffinity(cpuId: number): boolean;
29
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NativeServer = void 0;
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ let nativeBinding = null;
6
+ // Use try/catch with module.createRequire if import.meta is not available in some build targets
7
+ // However, since we are in a TS module that might be commonjs or esm
8
+ // We'll use a safer approach for require
9
+ try {
10
+ const req = require;
11
+ try {
12
+ nativeBinding = req('../../build/Release/qhttpx_native.node');
13
+ }
14
+ catch {
15
+ try {
16
+ nativeBinding = req('../../build/Debug/qhttpx_native.node');
17
+ }
18
+ catch {
19
+ // Ignored
20
+ }
21
+ }
22
+ }
23
+ catch {
24
+ // Ignored
25
+ }
26
+ class NativeServer {
27
+ constructor() {
28
+ if (nativeBinding && nativeBinding.NativeServer) {
29
+ this.binding = new nativeBinding.NativeServer();
30
+ }
31
+ else {
32
+ this.binding = null;
33
+ }
34
+ }
35
+ get isAvailable() {
36
+ return this.binding !== null;
37
+ }
38
+ parse(buffer) {
39
+ if (!this.binding)
40
+ throw new Error('Native bindings not available');
41
+ return this.binding.parse(buffer);
42
+ }
43
+ createResponse(statusCode, headers, body) {
44
+ if (!this.binding)
45
+ throw new Error('Native bindings not available');
46
+ return this.binding.createResponse(statusCode, headers, body);
47
+ }
48
+ createJSONResponse(obj) {
49
+ if (!this.binding)
50
+ throw new Error('Native bindings not available');
51
+ return this.binding.createJSONResponse(obj);
52
+ }
53
+ writeResponse(fd, chunks) {
54
+ if (!this.binding)
55
+ throw new Error('Native bindings not available');
56
+ return this.binding.writeResponse(fd, chunks);
57
+ }
58
+ setCPUAffinity(cpuId) {
59
+ if (!this.binding)
60
+ throw new Error('Native bindings not available');
61
+ return this.binding.setCPUAffinity(cpuId);
62
+ }
63
+ }
64
+ exports.NativeServer = NativeServer;
@@ -12,5 +12,7 @@ export declare class RadixTree {
12
12
  private root;
13
13
  insert(segments: string[], handler: QHTTPXHandler, priority: RoutePriority): void;
14
14
  lookup(segments: string[]): MatchResult | null;
15
+ lookupPath(path: string): MatchResult | null;
16
+ private findPath;
15
17
  private find;
16
18
  }
@@ -10,6 +10,8 @@ class Node {
10
10
  this.paramName = null;
11
11
  // Data if this node is a route end
12
12
  this.data = null;
13
+ // Optimization: Pre-allocated match result for static matches
14
+ this.staticMatch = null;
13
15
  }
14
16
  }
15
17
  class RadixTree {
@@ -38,14 +40,62 @@ class RadixTree {
38
40
  }
39
41
  }
40
42
  node.data = { handler, priority };
43
+ node.staticMatch = { handler, priority, params: {} };
41
44
  }
42
45
  lookup(segments) {
43
- // Use a stack-based approach or recursion.
44
- // For simplicity and correctness with backtracking, we'll use recursion.
45
- // To minimize allocations, we pass the same params object and only copy on success?
46
- // Actually, creating a params object is inevitable for the result.
47
46
  return this.find(this.root, segments, 0);
48
47
  }
48
+ lookupPath(path) {
49
+ if (path === '/' || path === '') {
50
+ if (this.root.staticMatch) {
51
+ return this.root.staticMatch;
52
+ }
53
+ return null;
54
+ }
55
+ const start = path.charCodeAt(0) === 47 ? 1 : 0;
56
+ return this.findPath(this.root, path, start);
57
+ }
58
+ findPath(node, path, start) {
59
+ // Handle trailing slash or end of path
60
+ if (start >= path.length) {
61
+ if (node.staticMatch) {
62
+ return node.staticMatch;
63
+ }
64
+ return null;
65
+ }
66
+ let end = path.indexOf('/', start);
67
+ if (end === -1) {
68
+ end = path.length;
69
+ }
70
+ const segment = path.substring(start, end);
71
+ const nextStart = end + 1;
72
+ // Skip empty segments (e.g. //) to match normalize behavior
73
+ if (segment.length === 0) {
74
+ return this.findPath(node, path, nextStart);
75
+ }
76
+ // 1. Try exact match
77
+ const child = node.children.get(segment);
78
+ if (child) {
79
+ const result = this.findPath(child, path, nextStart);
80
+ if (result)
81
+ return result;
82
+ }
83
+ // 2. Try param match
84
+ if (node.paramChild) {
85
+ const result = this.findPath(node.paramChild, path, nextStart);
86
+ if (result) {
87
+ // Clone params to avoid modifying shared staticMatch
88
+ const newParams = { ...result.params };
89
+ newParams[node.paramChild.paramName] = segment;
90
+ return {
91
+ handler: result.handler,
92
+ priority: result.priority,
93
+ params: newParams
94
+ };
95
+ }
96
+ }
97
+ return null;
98
+ }
49
99
  find(node, segments, index) {
50
100
  if (index === segments.length) {
51
101
  if (node.data) {
@@ -15,6 +15,7 @@ export type RouteMatch = {
15
15
  export declare class Router {
16
16
  private readonly methodBuckets;
17
17
  private readonly radixTrees;
18
+ private readonly staticRoutes;
18
19
  private isFrozen;
19
20
  register(method: HTTPMethod, path: string, handler: QHTTPXHandler, options?: RouteOptions & {
20
21
  schema?: RouteSchema | Record<string, unknown>;
@@ -17,6 +17,16 @@ class Router {
17
17
  ]);
18
18
  // Derived structures (built at freeze time)
19
19
  this.radixTrees = new Map();
20
+ // Fast lookup for static routes (no params)
21
+ this.staticRoutes = new Map([
22
+ ['GET', new Map()],
23
+ ['POST', new Map()],
24
+ ['PUT', new Map()],
25
+ ['DELETE', new Map()],
26
+ ['PATCH', new Map()],
27
+ ['HEAD', new Map()],
28
+ ['OPTIONS', new Map()],
29
+ ]);
20
30
  // Freeze state
21
31
  this.isFrozen = false;
22
32
  }
@@ -42,10 +52,18 @@ class Router {
42
52
  match(method, path) {
43
53
  // Fast path for frozen router
44
54
  if (this.isFrozen) {
55
+ // 1. Try static match (O(1))
56
+ const staticMap = this.staticRoutes.get(method);
57
+ if (staticMap) {
58
+ const match = staticMap.get(path);
59
+ if (match) {
60
+ return match;
61
+ }
62
+ }
63
+ // 2. Try radix tree match
45
64
  const tree = this.radixTrees.get(method);
46
65
  if (tree) {
47
- const segments = this.normalize(path);
48
- const match = tree.lookup(segments);
66
+ const match = tree.lookupPath(path);
49
67
  if (match) {
50
68
  return match;
51
69
  }
@@ -127,8 +145,30 @@ class Router {
127
145
  // Build derived structures for faster matching
128
146
  for (const [method, routes] of this.methodBuckets.entries()) {
129
147
  const tree = new radix_tree_1.RadixTree();
148
+ const staticMap = this.staticRoutes.get(method);
130
149
  for (const route of routes) {
131
150
  tree.insert(route.segments, route.handler, route.priority);
151
+ // Check if route is static (no params)
152
+ let isStatic = true;
153
+ for (const segment of route.segments) {
154
+ if (segment.startsWith(':')) {
155
+ isStatic = false;
156
+ break;
157
+ }
158
+ }
159
+ if (isStatic) {
160
+ // Optimization: Pre-allocate static match result
161
+ // Note: we use route.path directly as key
162
+ staticMap.set(route.path, {
163
+ handler: route.handler,
164
+ params: {},
165
+ priority: route.priority,
166
+ });
167
+ // Also handle trailing slash or no trailing slash?
168
+ // For strict matching, we use exact path.
169
+ // QHTTPX seems to normalize, so /json/ and /json might be different?
170
+ // route.path comes from user.
171
+ }
132
172
  }
133
173
  this.radixTrees.set(method, tree);
134
174
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const native_adapter_1 = require("../src/core/native-adapter");
8
+ const index_1 = require("../src/index");
9
+ const net_1 = __importDefault(require("net"));
10
+ // Mock the NativeServer module
11
+ vitest_1.vi.mock('../src/native', () => {
12
+ return {
13
+ NativeServer: class MockNativeServer {
14
+ constructor() {
15
+ this.isAvailable = true;
16
+ }
17
+ parse(buffer) {
18
+ const str = buffer.toString();
19
+ if (str.includes('GET / HTTP/1.1')) {
20
+ return {
21
+ method: 'GET',
22
+ path: '/',
23
+ version: 1,
24
+ headers: { host: 'localhost' },
25
+ bodyOffset: str.indexOf('\r\n\r\n') + 4
26
+ };
27
+ }
28
+ return undefined;
29
+ }
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ createResponse(statusCode, headers, body) {
32
+ return Buffer.from(`HTTP/1.1 ${statusCode} OK\r\n\r\n${body || ''}`);
33
+ }
34
+ }
35
+ };
36
+ });
37
+ (0, vitest_1.describe)('NativeAdapter', () => {
38
+ let app;
39
+ let adapter;
40
+ let server;
41
+ let port = 0;
42
+ (0, vitest_1.beforeEach)(() => {
43
+ app = (0, index_1.createHttpApp)();
44
+ adapter = new native_adapter_1.NativeAdapter(app);
45
+ });
46
+ (0, vitest_1.afterEach)(async () => {
47
+ if (server) {
48
+ await new Promise(resolve => server.close(() => resolve()));
49
+ }
50
+ });
51
+ (0, vitest_1.it)('should handle request using native parser mock', async () => {
52
+ app.get('/', ({ res }) => { res.end('Native Works'); });
53
+ await new Promise((resolve) => {
54
+ server = adapter.listen(0, () => {
55
+ port = server.address().port;
56
+ resolve();
57
+ });
58
+ });
59
+ const socket = net_1.default.createConnection(port);
60
+ const responsePromise = new Promise((resolve) => {
61
+ socket.on('data', (data) => {
62
+ resolve(data.toString());
63
+ socket.end();
64
+ });
65
+ });
66
+ socket.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n');
67
+ const response = await responsePromise;
68
+ (0, vitest_1.expect)(response).toContain('HTTP/1.1 200 OK');
69
+ (0, vitest_1.expect)(response).toContain('Native Works');
70
+ });
71
+ });
@@ -32,6 +32,9 @@ const index_1 = require("../src/index");
32
32
  maxMemoryBytes: 1000000,
33
33
  });
34
34
  const { port } = await app.listen(0, '127.0.0.1');
35
+ // Force memory check by manipulating requestCounter
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ app.requestCounter = 99;
35
38
  app.get('/', (ctx) => {
36
39
  ctx.send('ok');
37
40
  });
@@ -67,8 +67,8 @@ const src_1 = require("../src");
67
67
  });
68
68
  (0, vitest_1.it)('applies in-memory rate limiting per remote address', async () => {
69
69
  app = new src_1.QHTTPX();
70
- app.use((0, src_1.createRateLimitMiddleware)({
71
- maxRequests: 1,
70
+ app.use((0, src_1.rateLimit)({
71
+ max: 1,
72
72
  windowMs: 1000,
73
73
  }));
74
74
  app.get('/ping', (ctx) => {