qhttpx 1.9.1 → 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 +22 -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 +4 -15
  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,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachSignalHandlers = attachSignalHandlers;
4
+ function attachSignalHandlers(app, options = {}) {
5
+ const signals = options.signals ?? ['SIGINT', 'SIGTERM'];
6
+ const timeoutMs = options.timeoutMs ?? 10000;
7
+ let shuttingDown = false;
8
+ const handler = async (signal) => {
9
+ if (shuttingDown) {
10
+ return;
11
+ }
12
+ shuttingDown = true;
13
+ console.log(`Received ${signal}, shutting down...`);
14
+ const timer = setTimeout(() => {
15
+ console.error('Shutdown timed out, forcing exit. (some connections might be lost)');
16
+ process.exit(1);
17
+ }, timeoutMs);
18
+ try {
19
+ await app.shutdown();
20
+ clearTimeout(timer);
21
+ process.exit(0);
22
+ }
23
+ catch (err) {
24
+ console.error('Error during shutdown:', err);
25
+ process.exit(1);
26
+ }
27
+ };
28
+ for (const signal of signals) {
29
+ process.on(signal, handler);
30
+ }
31
+ }
@@ -0,0 +1,107 @@
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.createStaticMiddleware = createStaticMiddleware;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function guessContentType(filePath) {
10
+ const ext = path_1.default.extname(filePath).toLowerCase();
11
+ if (ext === '.html' || ext === '.htm') {
12
+ return 'text/html; charset=utf-8';
13
+ }
14
+ if (ext === '.js') {
15
+ return 'application/javascript; charset=utf-8';
16
+ }
17
+ if (ext === '.css') {
18
+ return 'text/css; charset=utf-8';
19
+ }
20
+ if (ext === '.json') {
21
+ return 'application/json; charset=utf-8';
22
+ }
23
+ if (ext === '.png') {
24
+ return 'image/png';
25
+ }
26
+ if (ext === '.jpg' || ext === '.jpeg') {
27
+ return 'image/jpeg';
28
+ }
29
+ if (ext === '.gif') {
30
+ return 'image/gif';
31
+ }
32
+ if (ext === '.svg') {
33
+ return 'image/svg+xml';
34
+ }
35
+ return 'application/octet-stream';
36
+ }
37
+ function createStaticMiddleware(options) {
38
+ const root = path_1.default.resolve(options.root);
39
+ const indexFile = options.index ?? 'index.html';
40
+ const fallthrough = options.fallthrough ?? false;
41
+ return async (ctx, next) => {
42
+ const method = ctx.req.method || 'GET';
43
+ if (method !== 'GET' && method !== 'HEAD') {
44
+ if (fallthrough) {
45
+ await next();
46
+ return;
47
+ }
48
+ ctx.res.statusCode = 405;
49
+ ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
50
+ ctx.res.end('Method Not Allowed');
51
+ return;
52
+ }
53
+ let requestPath = ctx.url.pathname || '/';
54
+ requestPath = requestPath.replace(/^[/\\]+/, '');
55
+ if (requestPath === '') {
56
+ requestPath = indexFile;
57
+ }
58
+ else if (requestPath.endsWith('/')) {
59
+ requestPath = requestPath + indexFile;
60
+ }
61
+ let safePath = path_1.default.normalize(requestPath);
62
+ safePath = safePath.replace(/^(\.\.(\/|\\|$))+/, '');
63
+ const filePath = path_1.default.join(root, safePath);
64
+ let stat;
65
+ try {
66
+ stat = await fs_1.default.promises.stat(filePath);
67
+ }
68
+ catch {
69
+ if (fallthrough) {
70
+ await next();
71
+ return;
72
+ }
73
+ ctx.res.statusCode = 404;
74
+ ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
75
+ ctx.res.end('Not Found');
76
+ return;
77
+ }
78
+ if (!stat.isFile()) {
79
+ if (fallthrough) {
80
+ await next();
81
+ return;
82
+ }
83
+ ctx.res.statusCode = 404;
84
+ ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
85
+ ctx.res.end('Not Found');
86
+ return;
87
+ }
88
+ const contentType = guessContentType(filePath);
89
+ ctx.res.setHeader('content-type', contentType);
90
+ ctx.res.statusCode = 200;
91
+ if (method === 'HEAD') {
92
+ ctx.res.end();
93
+ return;
94
+ }
95
+ try {
96
+ const data = await fs_1.default.promises.readFile(filePath);
97
+ ctx.res.end(data);
98
+ }
99
+ catch {
100
+ if (!ctx.res.headersSent) {
101
+ ctx.res.statusCode = 500;
102
+ ctx.res.setHeader('content-type', 'text/plain; charset=utf-8');
103
+ }
104
+ ctx.res.end('Internal Server Error');
105
+ }
106
+ };
107
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSseStream = createSseStream;
4
+ exports.sendStream = sendStream;
5
+ function createSseStream(ctx, options = {}) {
6
+ const res = ctx.res;
7
+ if (!res.headersSent) {
8
+ res.statusCode = 200;
9
+ res.setHeader('content-type', 'text/event-stream; charset=utf-8');
10
+ res.setHeader('cache-control', 'no-cache');
11
+ res.setHeader('connection', 'keep-alive');
12
+ }
13
+ const anyRes = res;
14
+ if (anyRes.flushHeaders) {
15
+ anyRes.flushHeaders();
16
+ }
17
+ if (typeof options.retryMs === 'number') {
18
+ res.write(`retry: ${options.retryMs}\n\n`);
19
+ }
20
+ const send = (data, event) => {
21
+ if (res.writableEnded) {
22
+ return;
23
+ }
24
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
25
+ let chunk = '';
26
+ if (event) {
27
+ chunk += `event: ${event}\n`;
28
+ }
29
+ chunk += `data: ${payload}\n\n`;
30
+ res.write(chunk);
31
+ if (anyRes.flush) {
32
+ anyRes.flush();
33
+ }
34
+ };
35
+ const close = () => {
36
+ if (!res.writableEnded) {
37
+ res.end();
38
+ }
39
+ };
40
+ return { send, close };
41
+ }
42
+ function sendStream(ctx, stream, options = {}) {
43
+ const res = ctx.res;
44
+ if (!res.headersSent) {
45
+ if (options.status !== undefined) {
46
+ res.statusCode = options.status;
47
+ }
48
+ if (options.contentType) {
49
+ res.setHeader('content-type', options.contentType);
50
+ }
51
+ }
52
+ return new Promise((resolve, reject) => {
53
+ stream.on('error', (err) => {
54
+ if (!res.headersSent) {
55
+ res.statusCode = 500;
56
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
57
+ }
58
+ if (!res.writableEnded) {
59
+ res.end('Internal Server Error');
60
+ }
61
+ reject(err);
62
+ });
63
+ stream.on('end', () => {
64
+ if (!res.writableEnded) {
65
+ res.end();
66
+ }
67
+ resolve();
68
+ });
69
+ stream.pipe(res, { end: false });
70
+ });
71
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskEngine = void 0;
4
+ class TaskEngine {
5
+ constructor(scheduler) {
6
+ this.tasks = new Map();
7
+ this.registeredTasksCount = 0;
8
+ this.totalEnqueued = 0;
9
+ this.totalCompleted = 0;
10
+ this.totalFailed = 0;
11
+ this.totalOverloaded = 0;
12
+ this.totalRetried = 0;
13
+ this.scheduler = scheduler;
14
+ }
15
+ register(name, handler, options = {}) {
16
+ if (!this.tasks.has(name)) {
17
+ this.registeredTasksCount += 1;
18
+ }
19
+ this.tasks.set(name, {
20
+ name,
21
+ handler,
22
+ options,
23
+ });
24
+ }
25
+ async enqueue(name, payload) {
26
+ const def = this.tasks.get(name);
27
+ if (!def) {
28
+ throw new Error(`Task "${name}" is not registered`);
29
+ }
30
+ this.totalEnqueued += 1;
31
+ await this.executeWithRetry(def, payload);
32
+ }
33
+ getMetrics() {
34
+ return {
35
+ registeredTasks: this.registeredTasksCount,
36
+ totalEnqueued: this.totalEnqueued,
37
+ totalCompleted: this.totalCompleted,
38
+ totalFailed: this.totalFailed,
39
+ totalOverloaded: this.totalOverloaded,
40
+ totalRetried: this.totalRetried,
41
+ };
42
+ }
43
+ async executeWithRetry(def, payload) {
44
+ const maxRetries = def.options.maxRetries ?? 0;
45
+ const backoffMs = def.options.backoffMs ?? 0;
46
+ let attempt = 0;
47
+ for (;;) {
48
+ let overloaded = false;
49
+ let error;
50
+ await this.scheduler.run(async () => {
51
+ try {
52
+ await def.handler(payload);
53
+ }
54
+ catch (err) {
55
+ error = err;
56
+ }
57
+ }, {
58
+ onOverloaded: () => {
59
+ overloaded = true;
60
+ },
61
+ });
62
+ if (!overloaded && !error) {
63
+ this.totalCompleted += 1;
64
+ return;
65
+ }
66
+ if (attempt >= maxRetries) {
67
+ if (error) {
68
+ this.totalFailed += 1;
69
+ throw error;
70
+ }
71
+ if (overloaded) {
72
+ this.totalOverloaded += 1;
73
+ throw new Error(`Task "${def.name}" overloaded`);
74
+ }
75
+ return;
76
+ }
77
+ attempt += 1;
78
+ this.totalRetried += 1;
79
+ if (backoffMs > 0) {
80
+ await new Promise((resolve) => {
81
+ setTimeout(resolve, backoffMs);
82
+ });
83
+ }
84
+ }
85
+ }
86
+ }
87
+ exports.TaskEngine = TaskEngine;
@@ -0,0 +1,25 @@
1
+ import { QHTTPX } from '../core/server';
2
+ import { HTTPMethod } from '../core/types';
3
+ export declare class TestClient {
4
+ private app;
5
+ private server;
6
+ private baseURL;
7
+ constructor(app: QHTTPX);
8
+ /**
9
+ * Starts the server on a random port.
10
+ * Automatically called by request methods if not started.
11
+ */
12
+ start(): Promise<void>;
13
+ stop(): Promise<void>;
14
+ request(method: HTTPMethod, path: string, options?: {
15
+ headers?: Record<string, string>;
16
+ body?: any;
17
+ query?: Record<string, string | number | boolean>;
18
+ }): Promise<Response>;
19
+ get(path: string, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
20
+ post(path: string, body?: any, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
21
+ put(path: string, body?: any, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
22
+ delete(path: string, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
23
+ patch(path: string, body?: any, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
24
+ }
25
+ export declare function createTestClient(app: QHTTPX): TestClient;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestClient = void 0;
4
+ exports.createTestClient = createTestClient;
5
+ class TestClient {
6
+ constructor(app) {
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ this.server = null;
9
+ this.baseURL = '';
10
+ this.app = app;
11
+ }
12
+ /**
13
+ * Starts the server on a random port.
14
+ * Automatically called by request methods if not started.
15
+ */
16
+ async start() {
17
+ if (this.server)
18
+ return;
19
+ const { port } = await this.app.listen(0);
20
+ this.server = this.app.serverInstance;
21
+ this.baseURL = `http://127.0.0.1:${port}`;
22
+ }
23
+ async stop() {
24
+ if (this.server) {
25
+ await this.app.close();
26
+ this.server = null;
27
+ }
28
+ }
29
+ async request(method, path, options = {}) {
30
+ if (!this.server) {
31
+ await this.start();
32
+ }
33
+ const url = new URL(path, this.baseURL);
34
+ if (options.query) {
35
+ Object.entries(options.query).forEach(([k, v]) => {
36
+ url.searchParams.append(k, String(v));
37
+ });
38
+ }
39
+ const headers = options.headers || {};
40
+ let bodyPayload;
41
+ if (options.body) {
42
+ if (typeof options.body === 'object' &&
43
+ !(options.body instanceof Uint8Array) &&
44
+ !(options.body instanceof ArrayBuffer) &&
45
+ !(options.body instanceof FormData) &&
46
+ !(options.body instanceof URLSearchParams)) {
47
+ bodyPayload = JSON.stringify(options.body);
48
+ if (!headers['content-type']) {
49
+ headers['content-type'] = 'application/json';
50
+ }
51
+ }
52
+ else {
53
+ bodyPayload = options.body;
54
+ }
55
+ }
56
+ return fetch(url, {
57
+ method,
58
+ headers,
59
+ body: bodyPayload,
60
+ });
61
+ }
62
+ get(path, options) {
63
+ return this.request('GET', path, options);
64
+ }
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ post(path, body, options) {
67
+ return this.request('POST', path, { ...options, body });
68
+ }
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ put(path, body, options) {
71
+ return this.request('PUT', path, { ...options, body });
72
+ }
73
+ delete(path, options) {
74
+ return this.request('DELETE', path, options);
75
+ }
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ patch(path, body, options) {
78
+ return this.request('PATCH', path, { ...options, body });
79
+ }
80
+ }
81
+ exports.TestClient = TestClient;
82
+ function createTestClient(app) {
83
+ return new TestClient(app);
84
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTestClient = createTestClient;
4
+ async function createTestClient(app) {
5
+ const { port } = await app.listen(0, '127.0.0.1');
6
+ const baseUrl = `http://127.0.0.1:${port}`;
7
+ const request = async (method, path, body, extraHeaders = {}) => {
8
+ const headers = { ...extraHeaders };
9
+ const init = {
10
+ method,
11
+ headers,
12
+ };
13
+ if (body !== undefined) {
14
+ if (typeof body === 'string') {
15
+ init.body = body;
16
+ }
17
+ else if (body instanceof Uint8Array || Buffer.isBuffer(body)) {
18
+ init.body = body;
19
+ }
20
+ else {
21
+ init.body = JSON.stringify(body);
22
+ if (!('content-type' in headers)) {
23
+ headers['content-type'] = 'application/json';
24
+ }
25
+ }
26
+ }
27
+ return fetch(`${baseUrl}${path}`, init);
28
+ };
29
+ return {
30
+ get: (path, headers) => request('GET', path, undefined, headers),
31
+ post: (path, body, headers) => request('POST', path, body, headers),
32
+ put: (path, body, headers) => request('PUT', path, body, headers),
33
+ delete: (path, headers) => request('DELETE', path, undefined, headers),
34
+ close: async () => {
35
+ await app.close();
36
+ },
37
+ app,
38
+ url: baseUrl,
39
+ };
40
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpError = exports.RoutePriority = void 0;
4
+ var RoutePriority;
5
+ (function (RoutePriority) {
6
+ RoutePriority["CRITICAL"] = "critical";
7
+ RoutePriority["STANDARD"] = "standard";
8
+ RoutePriority["BEST_EFFORT"] = "best-effort";
9
+ })(RoutePriority || (exports.RoutePriority = RoutePriority = {}));
10
+ class HttpError extends Error {
11
+ constructor(status, message, options = {}) {
12
+ super(message ?? 'HTTP Error');
13
+ this.status = status;
14
+ this.code = options.code;
15
+ this.details = options.details;
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ }
19
+ exports.HttpError = HttpError;
@@ -0,0 +1,3 @@
1
+ import { CookieOptions } from '../core/types';
2
+ export declare function parseCookies(header: string | undefined): Record<string, string>;
3
+ export declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCookies = parseCookies;
4
+ exports.serializeCookie = serializeCookie;
5
+ function parseCookies(header) {
6
+ const list = {};
7
+ if (!header) {
8
+ return list;
9
+ }
10
+ header.split(';').forEach((cookie) => {
11
+ const parts = cookie.split('=');
12
+ const name = parts.shift()?.trim();
13
+ if (name) {
14
+ const value = parts.join('=');
15
+ list[name] = decodeURIComponent(value);
16
+ }
17
+ });
18
+ return list;
19
+ }
20
+ function serializeCookie(name, value, options = {}) {
21
+ let str = `${name}=${encodeURIComponent(value)}`;
22
+ if (options.maxAge) {
23
+ str += `; Max-Age=${Math.floor(options.maxAge)}`;
24
+ }
25
+ if (options.domain) {
26
+ str += `; Domain=${options.domain}`;
27
+ }
28
+ if (options.path) {
29
+ str += `; Path=${options.path}`;
30
+ }
31
+ else {
32
+ str += '; Path=/';
33
+ }
34
+ if (options.expires) {
35
+ str += `; Expires=${options.expires.toUTCString()}`;
36
+ }
37
+ if (options.httpOnly) {
38
+ str += '; HttpOnly';
39
+ }
40
+ if (options.secure) {
41
+ str += '; Secure';
42
+ }
43
+ if (options.sameSite) {
44
+ switch (options.sameSite) {
45
+ case 'lax':
46
+ str += '; SameSite=Lax';
47
+ break;
48
+ case 'strict':
49
+ str += '; SameSite=Strict';
50
+ break;
51
+ case 'none':
52
+ str += '; SameSite=None';
53
+ break;
54
+ default:
55
+ break;
56
+ }
57
+ }
58
+ return str;
59
+ }
@@ -0,0 +1,12 @@
1
+ import { QHTTPXContext, QHTTPXMiddleware } from '../core/types';
2
+ export type LogEntry = {
3
+ method: string;
4
+ path: string;
5
+ status: number;
6
+ durationMs: number;
7
+ requestId?: string;
8
+ };
9
+ export type LoggerOptions = {
10
+ sink?: (entry: LogEntry, ctx: QHTTPXContext) => void;
11
+ };
12
+ export declare function createLoggerMiddleware(options?: LoggerOptions): QHTTPXMiddleware;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLoggerMiddleware = createLoggerMiddleware;
4
+ function createLoggerMiddleware(options = {}) {
5
+ const sink = options.sink ??
6
+ ((entry) => {
7
+ const status = entry.status;
8
+ let color = '\x1b[37m';
9
+ if (status >= 200 && status < 300) {
10
+ color = '\x1b[32m';
11
+ }
12
+ else if (status === 404) {
13
+ color = '\x1b[33m';
14
+ }
15
+ else if (status >= 500) {
16
+ color = '\x1b[34m';
17
+ }
18
+ else if (status >= 400) {
19
+ color = '\x1b[35m';
20
+ }
21
+ const reset = '\x1b[0m';
22
+ const prefix = entry.requestId ? `${entry.requestId} ` : '';
23
+ const line = `${prefix}${entry.method} ${entry.path} ${status} ${entry.durationMs}ms`;
24
+ console.log(`${color}${line}${reset}`);
25
+ });
26
+ return async (ctx, next) => {
27
+ const start = ctx.requestStart ?? Date.now();
28
+ try {
29
+ await next();
30
+ }
31
+ finally {
32
+ const durationMs = Math.max(1, Date.now() - start);
33
+ const method = ctx.req.method || 'GET';
34
+ const path = ctx.url.pathname;
35
+ const status = ctx.res.statusCode || 200;
36
+ sink({
37
+ method,
38
+ path,
39
+ status,
40
+ durationMs,
41
+ requestId: ctx.requestId,
42
+ }, ctx);
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,6 @@
1
+ import { QHTTPX } from '../core/server';
2
+ export type SignalHandlerOptions = {
3
+ signals?: NodeJS.Signals[];
4
+ timeoutMs?: number;
5
+ };
6
+ export declare function attachSignalHandlers(app: QHTTPX, options?: SignalHandlerOptions): void;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachSignalHandlers = attachSignalHandlers;
4
+ function attachSignalHandlers(app, options = {}) {
5
+ const signals = options.signals ?? ['SIGINT', 'SIGTERM'];
6
+ const timeoutMs = options.timeoutMs ?? 10000;
7
+ let shuttingDown = false;
8
+ const handler = async (signal) => {
9
+ if (shuttingDown) {
10
+ return;
11
+ }
12
+ shuttingDown = true;
13
+ console.log(`Received ${signal}, shutting down...`);
14
+ const timer = setTimeout(() => {
15
+ console.error('Shutdown timed out, forcing exit. (some connections might be lost)');
16
+ process.exit(1);
17
+ }, timeoutMs);
18
+ try {
19
+ await app.shutdown();
20
+ clearTimeout(timer);
21
+ process.exit(0);
22
+ }
23
+ catch (err) {
24
+ console.error('Error during shutdown:', err);
25
+ process.exit(1);
26
+ }
27
+ };
28
+ for (const signal of signals) {
29
+ process.on(signal, handler);
30
+ }
31
+ }
@@ -0,0 +1,6 @@
1
+ import { QHTTPXContext } from '../core/types';
2
+ export interface SSEStream {
3
+ send(data: unknown, event?: string, id?: string): void;
4
+ close(): void;
5
+ }
6
+ export declare function createSSE(ctx: QHTTPXContext): SSEStream;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSSE = createSSE;
4
+ function createSSE(ctx) {
5
+ const res = ctx.res;
6
+ // Disable auto-end so we can keep the connection open
7
+ ctx.disableAutoEnd = true;
8
+ // Only write headers if not already sent
9
+ if (!res.headersSent) {
10
+ res.setHeader('Content-Type', 'text/event-stream');
11
+ res.setHeader('Cache-Control', 'no-cache');
12
+ res.setHeader('Connection', 'keep-alive');
13
+ res.setHeader('X-Accel-Buffering', 'no'); // For Nginx
14
+ // Send initial ping/comment to flush headers and establish connection
15
+ res.write(': connected\n\n');
16
+ }
17
+ const send = (data, event, id) => {
18
+ if (id)
19
+ res.write(`id: ${id}\n`);
20
+ if (event)
21
+ res.write(`event: ${event}\n`);
22
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
23
+ res.write(`data: ${payload}\n\n`);
24
+ };
25
+ const close = () => {
26
+ res.end();
27
+ };
28
+ ctx.req.on('close', () => {
29
+ // console.log('SSE client disconnected');
30
+ });
31
+ return { send, close };
32
+ }