qhttpx 1.9.2 → 1.9.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.
Files changed (232) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +79 -17
  3. package/dist/examples/api-server.d.ts +1 -0
  4. package/dist/examples/api-server.js +77 -0
  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 +112 -0
  24. package/dist/src/benchmarks/compare-frameworks.js +119 -0
  25. package/dist/src/benchmarks/compare.d.ts +1 -0
  26. package/dist/src/benchmarks/compare.js +288 -0
  27. package/dist/src/benchmarks/quantam-users.d.ts +1 -0
  28. package/dist/src/benchmarks/quantam-users.js +56 -0
  29. package/dist/src/benchmarks/simple-json.d.ts +1 -0
  30. package/dist/src/benchmarks/simple-json.js +60 -0
  31. package/dist/src/benchmarks/ultra-mode.d.ts +1 -0
  32. package/dist/src/benchmarks/ultra-mode.js +94 -0
  33. package/dist/src/buffer-pool.js +70 -0
  34. package/dist/src/cli/index.d.ts +2 -0
  35. package/dist/src/cli/index.js +222 -0
  36. package/dist/src/client/index.d.ts +17 -0
  37. package/dist/src/client/index.js +72 -0
  38. package/dist/src/config.js +50 -0
  39. package/dist/src/cookies.js +59 -0
  40. package/dist/src/core/batch.d.ts +24 -0
  41. package/dist/src/core/batch.js +97 -0
  42. package/dist/src/core/body-parser.d.ts +15 -0
  43. package/dist/src/core/body-parser.js +121 -0
  44. package/dist/src/core/buffer-pool.d.ts +41 -0
  45. package/dist/src/core/buffer-pool.js +70 -0
  46. package/dist/src/core/config.d.ts +7 -0
  47. package/dist/src/core/config.js +50 -0
  48. package/dist/src/core/errors.d.ts +34 -0
  49. package/dist/src/core/errors.js +70 -0
  50. package/dist/src/core/fusion.d.ts +20 -0
  51. package/dist/src/core/fusion.js +193 -0
  52. package/dist/src/core/logger.d.ts +22 -0
  53. package/dist/src/core/logger.js +49 -0
  54. package/dist/src/core/metrics.d.ts +48 -0
  55. package/dist/src/core/metrics.js +117 -0
  56. package/dist/src/core/native-adapter.d.ts +11 -0
  57. package/dist/src/core/native-adapter.js +211 -0
  58. package/dist/src/core/resources.d.ts +9 -0
  59. package/dist/src/core/resources.js +25 -0
  60. package/dist/src/core/scheduler.d.ts +34 -0
  61. package/dist/src/core/scheduler.js +85 -0
  62. package/dist/src/core/scope.d.ts +26 -0
  63. package/dist/src/core/scope.js +68 -0
  64. package/dist/src/core/serializer.d.ts +10 -0
  65. package/dist/src/core/serializer.js +44 -0
  66. package/dist/src/core/server.d.ts +138 -0
  67. package/dist/src/core/server.js +1082 -0
  68. package/dist/src/core/stream.d.ts +15 -0
  69. package/dist/src/core/stream.js +71 -0
  70. package/dist/src/core/tasks.d.ts +29 -0
  71. package/dist/src/core/tasks.js +87 -0
  72. package/dist/src/core/types.d.ts +173 -0
  73. package/dist/src/core/types.js +19 -0
  74. package/dist/src/core/websocket.d.ts +25 -0
  75. package/dist/src/core/websocket.js +86 -0
  76. package/dist/src/core/worker-queue.d.ts +41 -0
  77. package/dist/src/core/worker-queue.js +73 -0
  78. package/dist/src/cors.js +66 -0
  79. package/dist/src/database/adapters/memory.d.ts +21 -0
  80. package/dist/src/database/adapters/memory.js +90 -0
  81. package/dist/src/database/adapters/mongo.d.ts +11 -0
  82. package/dist/src/database/adapters/mongo.js +141 -0
  83. package/dist/src/database/adapters/postgres.d.ts +10 -0
  84. package/dist/src/database/adapters/postgres.js +111 -0
  85. package/dist/src/database/adapters/sqlite.d.ts +10 -0
  86. package/dist/src/database/adapters/sqlite.js +42 -0
  87. package/dist/src/database/coalescer.d.ts +14 -0
  88. package/dist/src/database/coalescer.js +134 -0
  89. package/dist/src/database/manager.d.ts +35 -0
  90. package/dist/src/database/manager.js +87 -0
  91. package/dist/src/database/types.d.ts +20 -0
  92. package/dist/src/database/types.js +2 -0
  93. package/dist/src/index.d.ts +50 -0
  94. package/dist/src/index.js +91 -0
  95. package/dist/src/logger.js +45 -0
  96. package/dist/src/metrics.js +111 -0
  97. package/dist/src/middleware/compression.d.ts +2 -0
  98. package/dist/src/middleware/compression.js +133 -0
  99. package/dist/src/middleware/cors.d.ts +2 -0
  100. package/dist/src/middleware/cors.js +66 -0
  101. package/dist/src/middleware/presets.d.ts +16 -0
  102. package/dist/src/middleware/presets.js +52 -0
  103. package/dist/src/middleware/rate-limit.d.ts +14 -0
  104. package/dist/src/middleware/rate-limit.js +83 -0
  105. package/dist/src/middleware/security.d.ts +21 -0
  106. package/dist/src/middleware/security.js +69 -0
  107. package/dist/src/middleware/static.d.ts +11 -0
  108. package/dist/src/middleware/static.js +191 -0
  109. package/dist/src/native/index.d.ts +32 -0
  110. package/dist/src/native/index.js +141 -0
  111. package/dist/src/openapi/generator.d.ts +19 -0
  112. package/dist/src/openapi/generator.js +149 -0
  113. package/dist/src/presets.js +33 -0
  114. package/dist/src/radix-router.js +89 -0
  115. package/dist/src/radix-tree.js +81 -0
  116. package/dist/src/resources.js +25 -0
  117. package/dist/src/router/radix-router.d.ts +18 -0
  118. package/dist/src/router/radix-router.js +89 -0
  119. package/dist/src/router/radix-tree.d.ts +18 -0
  120. package/dist/src/router/radix-tree.js +131 -0
  121. package/dist/src/router/router.d.ts +34 -0
  122. package/dist/src/router/router.js +186 -0
  123. package/dist/src/router.js +138 -0
  124. package/dist/src/scheduler.js +85 -0
  125. package/dist/src/security.js +69 -0
  126. package/dist/src/server.js +685 -0
  127. package/dist/src/signals.js +31 -0
  128. package/dist/src/static.js +107 -0
  129. package/dist/src/stream.js +71 -0
  130. package/dist/src/tasks.js +87 -0
  131. package/dist/src/testing/index.d.ts +25 -0
  132. package/dist/src/testing/index.js +84 -0
  133. package/dist/src/testing.js +40 -0
  134. package/dist/src/types.js +19 -0
  135. package/dist/src/utils/cookies.d.ts +3 -0
  136. package/dist/src/utils/cookies.js +59 -0
  137. package/dist/src/utils/logger.d.ts +12 -0
  138. package/dist/src/utils/logger.js +45 -0
  139. package/dist/src/utils/signals.d.ts +6 -0
  140. package/dist/src/utils/signals.js +31 -0
  141. package/dist/src/utils/sse.d.ts +6 -0
  142. package/dist/src/utils/sse.js +32 -0
  143. package/dist/src/utils/testing.js +40 -0
  144. package/dist/src/validation/index.d.ts +3 -0
  145. package/dist/src/validation/index.js +19 -0
  146. package/dist/src/validation/simple.d.ts +5 -0
  147. package/dist/src/validation/simple.js +102 -0
  148. package/dist/src/validation/types.d.ts +32 -0
  149. package/dist/src/validation/types.js +12 -0
  150. package/dist/src/validation/zod.d.ts +4 -0
  151. package/dist/src/validation/zod.js +18 -0
  152. package/dist/src/views/index.d.ts +1 -0
  153. package/dist/src/views/index.js +17 -0
  154. package/dist/src/views/types.d.ts +3 -0
  155. package/dist/src/views/types.js +2 -0
  156. package/dist/src/worker-queue.js +73 -0
  157. package/dist/tests/adapters.test.d.ts +1 -0
  158. package/dist/tests/adapters.test.js +106 -0
  159. package/dist/tests/batch.test.d.ts +1 -0
  160. package/dist/tests/batch.test.js +117 -0
  161. package/dist/tests/body-parser.test.d.ts +1 -0
  162. package/dist/tests/body-parser.test.js +52 -0
  163. package/dist/tests/compression-sse.test.d.ts +1 -0
  164. package/dist/tests/compression-sse.test.js +87 -0
  165. package/dist/tests/cookies.test.d.ts +1 -0
  166. package/dist/tests/cookies.test.js +63 -0
  167. package/dist/tests/cors.test.d.ts +1 -0
  168. package/dist/tests/cors.test.js +55 -0
  169. package/dist/tests/database.test.d.ts +1 -0
  170. package/dist/tests/database.test.js +80 -0
  171. package/dist/tests/dx.test.d.ts +1 -0
  172. package/dist/tests/dx.test.js +114 -0
  173. package/dist/tests/ecosystem.test.d.ts +1 -0
  174. package/dist/tests/ecosystem.test.js +133 -0
  175. package/dist/tests/features.test.d.ts +1 -0
  176. package/dist/tests/features.test.js +47 -0
  177. package/dist/tests/fusion.test.d.ts +1 -0
  178. package/dist/tests/fusion.test.js +92 -0
  179. package/dist/tests/http-basic.test.d.ts +1 -0
  180. package/dist/tests/http-basic.test.js +124 -0
  181. package/dist/tests/logger.test.d.ts +1 -0
  182. package/dist/tests/logger.test.js +33 -0
  183. package/dist/tests/middleware.test.d.ts +1 -0
  184. package/dist/tests/middleware.test.js +109 -0
  185. package/dist/tests/native-adapter.test.d.ts +1 -0
  186. package/dist/tests/native-adapter.test.js +71 -0
  187. package/dist/tests/observability.test.d.ts +1 -0
  188. package/dist/tests/observability.test.js +59 -0
  189. package/dist/tests/openapi.test.d.ts +1 -0
  190. package/dist/tests/openapi.test.js +64 -0
  191. package/dist/tests/plugin.test.d.ts +1 -0
  192. package/dist/tests/plugin.test.js +65 -0
  193. package/dist/tests/plugins.test.d.ts +1 -0
  194. package/dist/tests/plugins.test.js +71 -0
  195. package/dist/tests/rate-limit.test.d.ts +1 -0
  196. package/dist/tests/rate-limit.test.js +77 -0
  197. package/dist/tests/resources.test.d.ts +1 -0
  198. package/dist/tests/resources.test.js +47 -0
  199. package/dist/tests/scheduler.test.d.ts +1 -0
  200. package/dist/tests/scheduler.test.js +46 -0
  201. package/dist/tests/schema-routes.test.d.ts +1 -0
  202. package/dist/tests/schema-routes.test.js +77 -0
  203. package/dist/tests/security.test.d.ts +1 -0
  204. package/dist/tests/security.test.js +83 -0
  205. package/dist/tests/server-db.test.d.ts +1 -0
  206. package/dist/tests/server-db.test.js +72 -0
  207. package/dist/tests/smoke.test.d.ts +1 -0
  208. package/dist/tests/smoke.test.js +10 -0
  209. package/dist/tests/sqlite-fusion.test.d.ts +1 -0
  210. package/dist/tests/sqlite-fusion.test.js +92 -0
  211. package/dist/tests/static.test.d.ts +1 -0
  212. package/dist/tests/static.test.js +102 -0
  213. package/dist/tests/stream.test.d.ts +1 -0
  214. package/dist/tests/stream.test.js +44 -0
  215. package/dist/tests/task-metrics.test.d.ts +1 -0
  216. package/dist/tests/task-metrics.test.js +53 -0
  217. package/dist/tests/tasks.test.d.ts +1 -0
  218. package/dist/tests/tasks.test.js +62 -0
  219. package/dist/tests/testing.test.d.ts +1 -0
  220. package/dist/tests/testing.test.js +47 -0
  221. package/dist/tests/validation.test.d.ts +1 -0
  222. package/dist/tests/validation.test.js +107 -0
  223. package/dist/tests/websocket.test.d.ts +1 -0
  224. package/dist/tests/websocket.test.js +146 -0
  225. package/dist/vitest.config.d.ts +2 -0
  226. package/dist/vitest.config.js +9 -0
  227. package/docs/FUSION.md +19 -0
  228. package/package.json +2 -1
  229. package/prebuilds/darwin-arm64/qhttpx.node +0 -0
  230. package/prebuilds/linux-x64/qhttpx.node +0 -0
  231. package/prebuilds/win32-x64/qhttpx.node +0 -0
  232. package/scripts/install-native.js +26 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Buffer pool for response bodies (small, medium, large).
3
+ * Reuses allocated buffers to reduce GC pressure.
4
+ */
5
+ export type BufferPoolConfig = {
6
+ smallSize?: number;
7
+ mediumSize?: number;
8
+ largeSize?: number;
9
+ smallPoolSize?: number;
10
+ mediumPoolSize?: number;
11
+ largePoolSize?: number;
12
+ };
13
+ export declare class BufferPool {
14
+ private readonly smallSize;
15
+ private readonly mediumSize;
16
+ private readonly largeSize;
17
+ private readonly smallBuffers;
18
+ private readonly mediumBuffers;
19
+ private readonly largeBuffers;
20
+ private readonly smallPoolSize;
21
+ private readonly mediumPoolSize;
22
+ private readonly largePoolSize;
23
+ constructor(config?: BufferPoolConfig);
24
+ /**
25
+ * Acquire a buffer suitable for the given size.
26
+ * Returns a buffer from the appropriate pool.
27
+ */
28
+ acquire(size: number): Buffer;
29
+ /**
30
+ * Release a buffer back to the appropriate pool.
31
+ */
32
+ release(buffer: Buffer): void;
33
+ /**
34
+ * Get the number of available buffers in each pool.
35
+ */
36
+ getPoolStatus(): {
37
+ small: number;
38
+ medium: number;
39
+ large: number;
40
+ };
41
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Buffer pool for response bodies (small, medium, large).
4
+ * Reuses allocated buffers to reduce GC pressure.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BufferPool = void 0;
8
+ class BufferPool {
9
+ constructor(config = {}) {
10
+ this.smallBuffers = [];
11
+ this.mediumBuffers = [];
12
+ this.largeBuffers = [];
13
+ this.smallSize = config.smallSize ?? 4096; // 4KB
14
+ this.mediumSize = config.mediumSize ?? 65536; // 64KB
15
+ this.largeSize = config.largeSize ?? 262144; // 256KB
16
+ this.smallPoolSize = config.smallPoolSize ?? 32;
17
+ this.mediumPoolSize = config.mediumPoolSize ?? 8;
18
+ this.largePoolSize = config.largePoolSize ?? 2;
19
+ // Preallocate buffers
20
+ for (let i = 0; i < this.smallPoolSize; i += 1) {
21
+ this.smallBuffers.push(Buffer.allocUnsafe(this.smallSize));
22
+ }
23
+ for (let i = 0; i < this.mediumPoolSize; i += 1) {
24
+ this.mediumBuffers.push(Buffer.allocUnsafe(this.mediumSize));
25
+ }
26
+ for (let i = 0; i < this.largePoolSize; i += 1) {
27
+ this.largeBuffers.push(Buffer.allocUnsafe(this.largeSize));
28
+ }
29
+ }
30
+ /**
31
+ * Acquire a buffer suitable for the given size.
32
+ * Returns a buffer from the appropriate pool.
33
+ */
34
+ acquire(size) {
35
+ if (size <= this.smallSize) {
36
+ return this.smallBuffers.pop() || Buffer.allocUnsafe(this.smallSize);
37
+ }
38
+ if (size <= this.mediumSize) {
39
+ return this.mediumBuffers.pop() || Buffer.allocUnsafe(this.mediumSize);
40
+ }
41
+ return this.largeBuffers.pop() || Buffer.allocUnsafe(this.largeSize);
42
+ }
43
+ /**
44
+ * Release a buffer back to the appropriate pool.
45
+ */
46
+ release(buffer) {
47
+ if (buffer.length === this.smallSize && this.smallBuffers.length < this.smallPoolSize) {
48
+ this.smallBuffers.push(buffer);
49
+ }
50
+ else if (buffer.length === this.mediumSize &&
51
+ this.mediumBuffers.length < this.mediumPoolSize) {
52
+ this.mediumBuffers.push(buffer);
53
+ }
54
+ else if (buffer.length === this.largeSize &&
55
+ this.largeBuffers.length < this.largePoolSize) {
56
+ this.largeBuffers.push(buffer);
57
+ }
58
+ }
59
+ /**
60
+ * Get the number of available buffers in each pool.
61
+ */
62
+ getPoolStatus() {
63
+ return {
64
+ small: this.smallBuffers.length,
65
+ medium: this.mediumBuffers.length,
66
+ large: this.largeBuffers.length,
67
+ };
68
+ }
69
+ }
70
+ exports.BufferPool = BufferPool;
@@ -0,0 +1,7 @@
1
+ import type { QHTTPXOptions } from './types';
2
+ export type LoadConfigOptions = {
3
+ env?: NodeJS.ProcessEnv;
4
+ defaults?: QHTTPXOptions;
5
+ prefix?: string;
6
+ };
7
+ export declare function loadConfig(options?: LoadConfigOptions): QHTTPXOptions;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadConfig = loadConfig;
4
+ function loadConfig(options = {}) {
5
+ const env = options.env ?? process.env;
6
+ const defaults = options.defaults ?? {};
7
+ const prefix = options.prefix ?? 'QHTTPX_';
8
+ const getNumber = (key) => {
9
+ const value = env[prefix + key];
10
+ if (!value) {
11
+ return undefined;
12
+ }
13
+ const n = Number(value);
14
+ if (!Number.isFinite(n) || n <= 0) {
15
+ return undefined;
16
+ }
17
+ return n;
18
+ };
19
+ const getBoolean = (key) => {
20
+ const value = env[prefix + key];
21
+ if (!value) {
22
+ return undefined;
23
+ }
24
+ const lower = value.toLowerCase();
25
+ if (lower === 'true' || lower === '1') {
26
+ return true;
27
+ }
28
+ if (lower === 'false' || lower === '0') {
29
+ return false;
30
+ }
31
+ return undefined;
32
+ };
33
+ const maxConcurrency = getNumber('MAX_CONCURRENCY');
34
+ const requestTimeoutMs = getNumber('REQUEST_TIMEOUT_MS');
35
+ const maxMemoryBytes = getNumber('MAX_MEMORY_BYTES');
36
+ const maxBodyBytes = getNumber('MAX_BODY_BYTES');
37
+ const keepAliveTimeoutMs = getNumber('KEEP_ALIVE_TIMEOUT_MS');
38
+ const headersTimeoutMs = getNumber('HEADERS_TIMEOUT_MS');
39
+ const metricsEnabled = getBoolean('METRICS_ENABLED');
40
+ return {
41
+ ...defaults,
42
+ maxConcurrency: maxConcurrency ?? defaults.maxConcurrency,
43
+ requestTimeoutMs: requestTimeoutMs ?? defaults.requestTimeoutMs,
44
+ maxMemoryBytes: maxMemoryBytes ?? defaults.maxMemoryBytes,
45
+ maxBodyBytes: maxBodyBytes ?? defaults.maxBodyBytes,
46
+ keepAliveTimeoutMs: keepAliveTimeoutMs ?? defaults.keepAliveTimeoutMs,
47
+ headersTimeoutMs: headersTimeoutMs ?? defaults.headersTimeoutMs,
48
+ metricsEnabled: metricsEnabled ?? defaults.metricsEnabled,
49
+ };
50
+ }
@@ -0,0 +1,34 @@
1
+ import { HttpError } from './types';
2
+ export declare class BadRequestException extends HttpError {
3
+ constructor(message?: string, details?: unknown);
4
+ }
5
+ export declare class UnauthorizedException extends HttpError {
6
+ constructor(message?: string, details?: unknown);
7
+ }
8
+ export declare class ForbiddenException extends HttpError {
9
+ constructor(message?: string, details?: unknown);
10
+ }
11
+ export declare class NotFoundException extends HttpError {
12
+ constructor(message?: string, details?: unknown);
13
+ }
14
+ export declare class MethodNotAllowedException extends HttpError {
15
+ constructor(message?: string, details?: unknown);
16
+ }
17
+ export declare class ConflictException extends HttpError {
18
+ constructor(message?: string, details?: unknown);
19
+ }
20
+ export declare class PayloadTooLargeException extends HttpError {
21
+ constructor(message?: string, details?: unknown);
22
+ }
23
+ export declare class UnsupportedMediaTypeException extends HttpError {
24
+ constructor(message?: string, details?: unknown);
25
+ }
26
+ export declare class TooManyRequestsException extends HttpError {
27
+ constructor(message?: string, details?: unknown);
28
+ }
29
+ export declare class InternalServerErrorException extends HttpError {
30
+ constructor(message?: string, details?: unknown);
31
+ }
32
+ export declare class ServiceUnavailableException extends HttpError {
33
+ constructor(message?: string, details?: unknown);
34
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServiceUnavailableException = exports.InternalServerErrorException = exports.TooManyRequestsException = exports.UnsupportedMediaTypeException = exports.PayloadTooLargeException = exports.ConflictException = exports.MethodNotAllowedException = exports.NotFoundException = exports.ForbiddenException = exports.UnauthorizedException = exports.BadRequestException = void 0;
4
+ const types_1 = require("./types");
5
+ class BadRequestException extends types_1.HttpError {
6
+ constructor(message = 'Bad Request', details) {
7
+ super(400, message, { code: 'BAD_REQUEST', details });
8
+ }
9
+ }
10
+ exports.BadRequestException = BadRequestException;
11
+ class UnauthorizedException extends types_1.HttpError {
12
+ constructor(message = 'Unauthorized', details) {
13
+ super(401, message, { code: 'UNAUTHORIZED', details });
14
+ }
15
+ }
16
+ exports.UnauthorizedException = UnauthorizedException;
17
+ class ForbiddenException extends types_1.HttpError {
18
+ constructor(message = 'Forbidden', details) {
19
+ super(403, message, { code: 'FORBIDDEN', details });
20
+ }
21
+ }
22
+ exports.ForbiddenException = ForbiddenException;
23
+ class NotFoundException extends types_1.HttpError {
24
+ constructor(message = 'Not Found', details) {
25
+ super(404, message, { code: 'NOT_FOUND', details });
26
+ }
27
+ }
28
+ exports.NotFoundException = NotFoundException;
29
+ class MethodNotAllowedException extends types_1.HttpError {
30
+ constructor(message = 'Method Not Allowed', details) {
31
+ super(405, message, { code: 'METHOD_NOT_ALLOWED', details });
32
+ }
33
+ }
34
+ exports.MethodNotAllowedException = MethodNotAllowedException;
35
+ class ConflictException extends types_1.HttpError {
36
+ constructor(message = 'Conflict', details) {
37
+ super(409, message, { code: 'CONFLICT', details });
38
+ }
39
+ }
40
+ exports.ConflictException = ConflictException;
41
+ class PayloadTooLargeException extends types_1.HttpError {
42
+ constructor(message = 'Payload Too Large', details) {
43
+ super(413, message, { code: 'PAYLOAD_TOO_LARGE', details });
44
+ }
45
+ }
46
+ exports.PayloadTooLargeException = PayloadTooLargeException;
47
+ class UnsupportedMediaTypeException extends types_1.HttpError {
48
+ constructor(message = 'Unsupported Media Type', details) {
49
+ super(415, message, { code: 'UNSUPPORTED_MEDIA_TYPE', details });
50
+ }
51
+ }
52
+ exports.UnsupportedMediaTypeException = UnsupportedMediaTypeException;
53
+ class TooManyRequestsException extends types_1.HttpError {
54
+ constructor(message = 'Too Many Requests', details) {
55
+ super(429, message, { code: 'TOO_MANY_REQUESTS', details });
56
+ }
57
+ }
58
+ exports.TooManyRequestsException = TooManyRequestsException;
59
+ class InternalServerErrorException extends types_1.HttpError {
60
+ constructor(message = 'Internal Server Error', details) {
61
+ super(500, message, { code: 'INTERNAL_SERVER_ERROR', details });
62
+ }
63
+ }
64
+ exports.InternalServerErrorException = InternalServerErrorException;
65
+ class ServiceUnavailableException extends types_1.HttpError {
66
+ constructor(message = 'Service Unavailable', details) {
67
+ super(503, message, { code: 'SERVICE_UNAVAILABLE', details });
68
+ }
69
+ }
70
+ exports.ServiceUnavailableException = ServiceUnavailableException;
@@ -0,0 +1,20 @@
1
+ import type { QHTTPXContext } from './types';
2
+ export interface RequestFusionOptions {
3
+ windowMs?: number;
4
+ vary?: string[];
5
+ }
6
+ export interface RequestFusionMetrics {
7
+ totalFused: number;
8
+ totalProcessed: number;
9
+ }
10
+ export declare class RequestFusion {
11
+ private inflight;
12
+ private cache;
13
+ private options;
14
+ private metrics;
15
+ constructor(options?: boolean | RequestFusionOptions);
16
+ getMetrics(): RequestFusionMetrics;
17
+ coalesce(ctx: QHTTPXContext, next: (ctx: QHTTPXContext) => Promise<void> | void): Promise<void>;
18
+ private applyResult;
19
+ private getKey;
20
+ }
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RequestFusion = void 0;
4
+ const crypto_1 = require("crypto");
5
+ class RequestFusion {
6
+ constructor(options = {}) {
7
+ this.inflight = new Map();
8
+ this.cache = new Map();
9
+ this.metrics = {
10
+ totalFused: 0,
11
+ totalProcessed: 0
12
+ };
13
+ this.options = typeof options === 'boolean' ? {} : options;
14
+ if (!this.options.vary) {
15
+ this.options.vary = ['authorization', 'cookie'];
16
+ }
17
+ }
18
+ getMetrics() {
19
+ return { ...this.metrics };
20
+ }
21
+ async coalesce(ctx, next) {
22
+ const key = this.getKey(ctx);
23
+ // 1. Check Cache (Short-lived "Micro-TTL")
24
+ if (this.options.windowMs && this.options.windowMs > 0) {
25
+ const cached = this.cache.get(key);
26
+ if (cached) {
27
+ if (Date.now() < cached.expires) {
28
+ this.metrics.totalFused++;
29
+ this.applyResult(ctx, cached.result);
30
+ return;
31
+ }
32
+ else {
33
+ this.cache.delete(key);
34
+ }
35
+ }
36
+ }
37
+ // 2. Check In-Flight
38
+ if (this.inflight.has(key)) {
39
+ try {
40
+ this.metrics.totalFused++;
41
+ const result = await this.inflight.get(key);
42
+ this.applyResult(ctx, result);
43
+ return;
44
+ }
45
+ catch (err) {
46
+ // If leader failed, we should probably fail too or retry?
47
+ // For now, let's propagate the error.
48
+ throw err;
49
+ }
50
+ }
51
+ // 3. Be the Leader
52
+ this.metrics.totalProcessed++;
53
+ let resolve;
54
+ let reject;
55
+ const promise = new Promise((res, rej) => {
56
+ resolve = res;
57
+ reject = rej;
58
+ });
59
+ this.inflight.set(key, promise);
60
+ // Hijack context methods to capture result
61
+ // Cast to any to allow overwriting readonly methods
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ const mutableCtx = ctx;
64
+ const originalJson = mutableCtx.json;
65
+ const originalSend = mutableCtx.send;
66
+ const originalHtml = mutableCtx.html;
67
+ const originalRedirect = mutableCtx.redirect;
68
+ let captured = false;
69
+ // Helper to cleanup and resolve
70
+ const finish = (result) => {
71
+ if (captured)
72
+ return;
73
+ captured = true;
74
+ resolve(result);
75
+ // Cache if needed
76
+ if (this.options.windowMs && this.options.windowMs > 0) {
77
+ this.cache.set(key, {
78
+ result,
79
+ expires: Date.now() + this.options.windowMs
80
+ });
81
+ // Cleanup cache later
82
+ setTimeout(() => this.cache.delete(key), this.options.windowMs);
83
+ }
84
+ // Remove from inflight immediately (unless we rely on cache for window)
85
+ // Actually, if we have windowMs, we rely on cache.
86
+ // If windowMs=0, we remove immediately.
87
+ this.inflight.delete(key);
88
+ };
89
+ mutableCtx.json = (payload, status = 200) => {
90
+ finish({ type: 'json', payload, status });
91
+ return originalJson.call(ctx, payload, status);
92
+ };
93
+ mutableCtx.send = (payload, status = 200) => {
94
+ finish({ type: 'send', payload, status });
95
+ return originalSend.call(ctx, payload, status);
96
+ };
97
+ mutableCtx.html = (payload, status = 200) => {
98
+ finish({ type: 'html', payload, status });
99
+ return originalHtml.call(ctx, payload, status);
100
+ };
101
+ mutableCtx.redirect = (url, status = 302) => {
102
+ finish({ type: 'redirect', payload: url, status });
103
+ return originalRedirect.call(ctx, url, status);
104
+ };
105
+ try {
106
+ await next(ctx);
107
+ // If next() completes but no response method was called,
108
+ // it implies the handler might be async and forgot to await,
109
+ // or it's a 404/middleware issue.
110
+ // If not captured yet, we can't resolve the followers properly.
111
+ // They will hang unless we reject or resolve with something.
112
+ // However, typical QHTTPX usage implies ctx.json/send is called.
113
+ }
114
+ catch (err) {
115
+ reject(err);
116
+ this.inflight.delete(key);
117
+ // Restore methods
118
+ mutableCtx.json = originalJson;
119
+ mutableCtx.send = originalSend;
120
+ mutableCtx.html = originalHtml;
121
+ mutableCtx.redirect = originalRedirect;
122
+ throw err;
123
+ }
124
+ }
125
+ applyResult(ctx, result) {
126
+ switch (result.type) {
127
+ case 'json':
128
+ ctx.json(result.payload, result.status);
129
+ break;
130
+ case 'send': {
131
+ if (typeof result.payload === 'string' || Buffer.isBuffer(result.payload)) {
132
+ ctx.send(result.payload, result.status);
133
+ }
134
+ else {
135
+ ctx.send(String(result.payload), result.status);
136
+ }
137
+ break;
138
+ }
139
+ case 'html': {
140
+ if (typeof result.payload === 'string') {
141
+ ctx.html(result.payload, result.status);
142
+ }
143
+ else {
144
+ ctx.html(String(result.payload), result.status);
145
+ }
146
+ break;
147
+ }
148
+ case 'redirect': {
149
+ if (typeof result.payload === 'string') {
150
+ ctx.redirect(result.payload, result.status);
151
+ }
152
+ else {
153
+ ctx.redirect(String(result.payload), result.status);
154
+ }
155
+ break;
156
+ }
157
+ }
158
+ }
159
+ getKey(ctx) {
160
+ // Base: Method + Path
161
+ let data = `${ctx.req.method}|${ctx.url?.pathname}|`;
162
+ // Query
163
+ if (ctx.query) {
164
+ // Deterministic sort
165
+ const keys = Object.keys(ctx.query).sort();
166
+ for (const k of keys) {
167
+ data += `${k}=${ctx.query[k]}|`;
168
+ }
169
+ }
170
+ // Body (if parsed)
171
+ if (ctx.body) {
172
+ // We assume body is simple JSON.
173
+ // For objects, order matters for hashing.
174
+ // fast-json-stringify or JSON.stringify isn't always deterministic key order.
175
+ // But for performance, JSON.stringify is often "good enough" if keys aren't shuffled.
176
+ // For strict correctness, we should use a canonical stringify, but that's slow.
177
+ // Let's use JSON.stringify for now.
178
+ data += JSON.stringify(ctx.body);
179
+ }
180
+ // Vary Headers
181
+ if (this.options.vary) {
182
+ for (const header of this.options.vary) {
183
+ const val = ctx.req.headers[header.toLowerCase()];
184
+ if (val) {
185
+ data += `|${header}:${val}`;
186
+ }
187
+ }
188
+ }
189
+ // We hash it to keep the key size manageable
190
+ return (0, crypto_1.createHash)('sha1').update(data).digest('hex');
191
+ }
192
+ }
193
+ exports.RequestFusion = RequestFusion;
@@ -0,0 +1,22 @@
1
+ import pino from 'pino';
2
+ export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
3
+ export interface LoggerOptions {
4
+ level?: LogLevel;
5
+ pretty?: boolean;
6
+ name?: string;
7
+ }
8
+ export declare class Logger {
9
+ private pino;
10
+ constructor(options?: LoggerOptions);
11
+ info(msg: string, ...args: any[]): void;
12
+ info(obj: object, msg?: string, ...args: any[]): void;
13
+ error(msg: string, ...args: any[]): void;
14
+ error(obj: object, msg?: string, ...args: any[]): void;
15
+ warn(msg: string, ...args: any[]): void;
16
+ warn(obj: object, msg?: string, ...args: any[]): void;
17
+ debug(msg: string, ...args: any[]): void;
18
+ debug(obj: object, msg?: string, ...args: any[]): void;
19
+ fatal(msg: string, ...args: any[]): void;
20
+ fatal(obj: object, msg?: string, ...args: any[]): void;
21
+ child(bindings: pino.Bindings): Logger;
22
+ }
@@ -0,0 +1,49 @@
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
+ exports.Logger = void 0;
7
+ /* eslint-disable @typescript-eslint/no-explicit-any */
8
+ const pino_1 = __importDefault(require("pino"));
9
+ class Logger {
10
+ constructor(options = {}) {
11
+ const isDev = process.env.NODE_ENV !== 'production';
12
+ const pretty = options.pretty ?? isDev;
13
+ this.pino = (0, pino_1.default)({
14
+ name: options.name || 'qhttpx',
15
+ level: options.level || 'info',
16
+ transport: pretty
17
+ ? {
18
+ target: 'pino-pretty',
19
+ options: {
20
+ colorize: true,
21
+ translateTime: 'HH:MM:ss Z',
22
+ ignore: 'pid,hostname',
23
+ },
24
+ }
25
+ : undefined,
26
+ });
27
+ }
28
+ info(arg1, ...args) {
29
+ this.pino.info(arg1, ...args);
30
+ }
31
+ error(arg1, ...args) {
32
+ this.pino.error(arg1, ...args);
33
+ }
34
+ warn(arg1, ...args) {
35
+ this.pino.warn(arg1, ...args);
36
+ }
37
+ debug(arg1, ...args) {
38
+ this.pino.debug(arg1, ...args);
39
+ }
40
+ fatal(arg1, ...args) {
41
+ this.pino.fatal(arg1, ...args);
42
+ }
43
+ child(bindings) {
44
+ const child = new Logger();
45
+ child.pino = this.pino.child(bindings);
46
+ return child;
47
+ }
48
+ }
49
+ exports.Logger = Logger;
@@ -0,0 +1,48 @@
1
+ import type { TaskEngine, TaskMetrics } from './tasks';
2
+ import { Scheduler } from './scheduler';
3
+ import type { RequestFusion, RequestFusionMetrics } from './fusion';
4
+ export type LatencySnapshot = {
5
+ p50: number | null;
6
+ p95: number | null;
7
+ p99: number | null;
8
+ };
9
+ export type MetricsSnapshot = {
10
+ totalRequests: number;
11
+ inFlightRequests: number;
12
+ totalErrors: number;
13
+ totalTimeouts: number;
14
+ requestsPerSecond: number;
15
+ latency: LatencySnapshot;
16
+ scheduler: {
17
+ inFlight: number;
18
+ };
19
+ tasks?: TaskMetrics;
20
+ fusion?: RequestFusionMetrics;
21
+ memory: {
22
+ rssBytes: number;
23
+ heapUsedBytes: number;
24
+ };
25
+ };
26
+ export declare class Metrics {
27
+ private readonly scheduler;
28
+ private readonly taskEngine?;
29
+ private readonly fusion?;
30
+ private readonly latencies;
31
+ private readonly maxLatencies;
32
+ private readonly enabled;
33
+ private totalRequests;
34
+ private inFlightRequests;
35
+ private totalErrors;
36
+ private totalTimeouts;
37
+ constructor(scheduler: Scheduler, options?: {
38
+ maxLatencies?: number;
39
+ enabled?: boolean;
40
+ }, taskEngine?: TaskEngine, fusion?: RequestFusion);
41
+ onRequestStart(): void;
42
+ onRequestEnd(durationMs: number, statusCode: number): void;
43
+ onTimeout(): void;
44
+ snapshot(): MetricsSnapshot;
45
+ private recordLatency;
46
+ private latencySnapshot;
47
+ private percentile;
48
+ }