qhttpx 2.1.0 → 2.3.1

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 (267) hide show
  1. package/LICENSE +201 -21
  2. package/README.md +117 -221
  3. package/dist/chunk-QW72SEAS.mjs +98 -0
  4. package/dist/{src/cli/index.d.ts → cli.d.mts} +0 -1
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +287 -0
  7. package/dist/cli.mjs +209 -0
  8. package/dist/index.d.mts +433 -0
  9. package/dist/index.d.ts +433 -0
  10. package/dist/index.js +1955 -0
  11. package/dist/index.mjs +1863 -0
  12. package/dist/qhttpx-core-new.linux-x64-gnu.node +0 -0
  13. package/dist/qhttpx-core-new.node +0 -0
  14. package/dist/qhttpx-core-new.win32-x64-msvc.node +0 -0
  15. package/examples/benchmark.ts +67 -0
  16. package/examples/body_demo.ts +32 -0
  17. package/examples/chat_client.ts +36 -0
  18. package/examples/chat_demo.ts +41 -0
  19. package/examples/cluster_demo.ts +26 -0
  20. package/examples/cors_demo.ts +25 -0
  21. package/examples/db_auth_demo.ts +66 -0
  22. package/examples/demo.ts +52 -0
  23. package/examples/headers_demo.ts +24 -0
  24. package/examples/hello.ts +7 -0
  25. package/examples/http2_demo.ts +52 -0
  26. package/examples/magic-dev.ts +15 -0
  27. package/examples/middleware_demo.ts +34 -0
  28. package/examples/mongo_demo.ts +40 -0
  29. package/examples/native_policy_demo.ts +33 -0
  30. package/examples/observability_demo.ts +24 -0
  31. package/examples/public/1mb.dat +1 -0
  32. package/examples/query_demo.ts +29 -0
  33. package/examples/response_demo.ts +21 -0
  34. package/examples/routing_demo.ts +24 -0
  35. package/examples/server.ts +51 -0
  36. package/examples/static_demo.ts +29 -0
  37. package/examples/test_middleware_client.ts +33 -0
  38. package/examples/test_query_client.ts +34 -0
  39. package/examples/test_response_client.ts +30 -0
  40. package/examples/tls_demo.ts +32 -0
  41. package/examples/upload_demo.ts +43 -0
  42. package/examples/uploads/test.txt +1 -0
  43. package/examples/verify_upload.ts +69 -0
  44. package/examples/ws_client.ts +26 -0
  45. package/examples/ws_demo.ts +21 -0
  46. package/package.json +65 -81
  47. package/CHANGELOG.md +0 -285
  48. package/dist/examples/api-server.d.ts +0 -1
  49. package/dist/examples/api-server.js +0 -80
  50. package/dist/examples/basic.d.ts +0 -1
  51. package/dist/examples/basic.js +0 -9
  52. package/dist/examples/compression.d.ts +0 -1
  53. package/dist/examples/compression.js +0 -15
  54. package/dist/examples/cors.d.ts +0 -1
  55. package/dist/examples/cors.js +0 -18
  56. package/dist/examples/errors.d.ts +0 -1
  57. package/dist/examples/errors.js +0 -26
  58. package/dist/examples/file-upload.d.ts +0 -1
  59. package/dist/examples/file-upload.js +0 -22
  60. package/dist/examples/fusion.d.ts +0 -1
  61. package/dist/examples/fusion.js +0 -21
  62. package/dist/examples/rate-limiting.d.ts +0 -1
  63. package/dist/examples/rate-limiting.js +0 -17
  64. package/dist/examples/validation.d.ts +0 -1
  65. package/dist/examples/validation.js +0 -22
  66. package/dist/examples/websockets.d.ts +0 -1
  67. package/dist/examples/websockets.js +0 -19
  68. package/dist/package.json +0 -107
  69. package/dist/src/benchmarks/quantam-users.d.ts +0 -1
  70. package/dist/src/benchmarks/quantam-users.js +0 -56
  71. package/dist/src/benchmarks/quick-bench.d.ts +0 -1
  72. package/dist/src/benchmarks/quick-bench.js +0 -57
  73. package/dist/src/benchmarks/simple-json.d.ts +0 -1
  74. package/dist/src/benchmarks/simple-json.js +0 -171
  75. package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
  76. package/dist/src/benchmarks/ultra-mode.js +0 -64
  77. package/dist/src/cli/index.js +0 -222
  78. package/dist/src/client/index.d.ts +0 -17
  79. package/dist/src/client/index.js +0 -72
  80. package/dist/src/core/batch.d.ts +0 -24
  81. package/dist/src/core/batch.js +0 -97
  82. package/dist/src/core/body-parser.d.ts +0 -15
  83. package/dist/src/core/body-parser.js +0 -121
  84. package/dist/src/core/buffer-pool.d.ts +0 -41
  85. package/dist/src/core/buffer-pool.js +0 -70
  86. package/dist/src/core/config.d.ts +0 -7
  87. package/dist/src/core/config.js +0 -50
  88. package/dist/src/core/context-pool.d.ts +0 -12
  89. package/dist/src/core/context-pool.js +0 -34
  90. package/dist/src/core/errors.d.ts +0 -34
  91. package/dist/src/core/errors.js +0 -70
  92. package/dist/src/core/fusion.d.ts +0 -20
  93. package/dist/src/core/fusion.js +0 -191
  94. package/dist/src/core/logger.d.ts +0 -22
  95. package/dist/src/core/logger.js +0 -49
  96. package/dist/src/core/metrics.d.ts +0 -50
  97. package/dist/src/core/metrics.js +0 -123
  98. package/dist/src/core/resources.d.ts +0 -9
  99. package/dist/src/core/resources.js +0 -25
  100. package/dist/src/core/scheduler.d.ts +0 -38
  101. package/dist/src/core/scheduler.js +0 -126
  102. package/dist/src/core/scope.d.ts +0 -41
  103. package/dist/src/core/scope.js +0 -107
  104. package/dist/src/core/serializer.d.ts +0 -10
  105. package/dist/src/core/serializer.js +0 -82
  106. package/dist/src/core/server.d.ts +0 -179
  107. package/dist/src/core/server.js +0 -1511
  108. package/dist/src/core/stream.d.ts +0 -15
  109. package/dist/src/core/stream.js +0 -71
  110. package/dist/src/core/tasks.d.ts +0 -29
  111. package/dist/src/core/tasks.js +0 -87
  112. package/dist/src/core/timer.d.ts +0 -11
  113. package/dist/src/core/timer.js +0 -29
  114. package/dist/src/core/types.d.ts +0 -225
  115. package/dist/src/core/types.js +0 -19
  116. package/dist/src/core/websocket.d.ts +0 -25
  117. package/dist/src/core/websocket.js +0 -86
  118. package/dist/src/core/worker-queue.d.ts +0 -41
  119. package/dist/src/core/worker-queue.js +0 -73
  120. package/dist/src/database/adapters/memory.d.ts +0 -21
  121. package/dist/src/database/adapters/memory.js +0 -90
  122. package/dist/src/database/adapters/mongo.d.ts +0 -11
  123. package/dist/src/database/adapters/mongo.js +0 -141
  124. package/dist/src/database/adapters/postgres.d.ts +0 -10
  125. package/dist/src/database/adapters/postgres.js +0 -111
  126. package/dist/src/database/adapters/sqlite.d.ts +0 -10
  127. package/dist/src/database/adapters/sqlite.js +0 -42
  128. package/dist/src/database/coalescer.d.ts +0 -14
  129. package/dist/src/database/coalescer.js +0 -134
  130. package/dist/src/database/manager.d.ts +0 -35
  131. package/dist/src/database/manager.js +0 -87
  132. package/dist/src/database/types.d.ts +0 -20
  133. package/dist/src/database/types.js +0 -2
  134. package/dist/src/index.d.ts +0 -52
  135. package/dist/src/index.js +0 -92
  136. package/dist/src/middleware/compression.d.ts +0 -2
  137. package/dist/src/middleware/compression.js +0 -133
  138. package/dist/src/middleware/cors.d.ts +0 -2
  139. package/dist/src/middleware/cors.js +0 -66
  140. package/dist/src/middleware/presets.d.ts +0 -15
  141. package/dist/src/middleware/presets.js +0 -52
  142. package/dist/src/middleware/rate-limit.d.ts +0 -14
  143. package/dist/src/middleware/rate-limit.js +0 -83
  144. package/dist/src/middleware/security.d.ts +0 -10
  145. package/dist/src/middleware/security.js +0 -74
  146. package/dist/src/middleware/static.d.ts +0 -11
  147. package/dist/src/middleware/static.js +0 -191
  148. package/dist/src/openapi/generator.d.ts +0 -19
  149. package/dist/src/openapi/generator.js +0 -149
  150. package/dist/src/router/radix-router.d.ts +0 -18
  151. package/dist/src/router/radix-router.js +0 -89
  152. package/dist/src/router/radix-tree.d.ts +0 -21
  153. package/dist/src/router/radix-tree.js +0 -175
  154. package/dist/src/router/router.d.ts +0 -37
  155. package/dist/src/router/router.js +0 -203
  156. package/dist/src/testing/index.d.ts +0 -25
  157. package/dist/src/testing/index.js +0 -84
  158. package/dist/src/utils/cookies.d.ts +0 -3
  159. package/dist/src/utils/cookies.js +0 -59
  160. package/dist/src/utils/logger.d.ts +0 -2
  161. package/dist/src/utils/logger.js +0 -45
  162. package/dist/src/utils/signals.d.ts +0 -6
  163. package/dist/src/utils/signals.js +0 -31
  164. package/dist/src/utils/sse.d.ts +0 -6
  165. package/dist/src/utils/sse.js +0 -32
  166. package/dist/src/validation/index.d.ts +0 -3
  167. package/dist/src/validation/index.js +0 -19
  168. package/dist/src/validation/simple.d.ts +0 -5
  169. package/dist/src/validation/simple.js +0 -102
  170. package/dist/src/validation/types.d.ts +0 -32
  171. package/dist/src/validation/types.js +0 -12
  172. package/dist/src/validation/zod.d.ts +0 -4
  173. package/dist/src/validation/zod.js +0 -18
  174. package/dist/src/views/index.d.ts +0 -1
  175. package/dist/src/views/index.js +0 -17
  176. package/dist/src/views/types.d.ts +0 -3
  177. package/dist/src/views/types.js +0 -2
  178. package/dist/tests/adapters.test.d.ts +0 -1
  179. package/dist/tests/adapters.test.js +0 -106
  180. package/dist/tests/batch.test.d.ts +0 -1
  181. package/dist/tests/batch.test.js +0 -117
  182. package/dist/tests/body-parser.test.d.ts +0 -1
  183. package/dist/tests/body-parser.test.js +0 -52
  184. package/dist/tests/compression-sse.test.d.ts +0 -1
  185. package/dist/tests/compression-sse.test.js +0 -87
  186. package/dist/tests/cookies.test.d.ts +0 -1
  187. package/dist/tests/cookies.test.js +0 -63
  188. package/dist/tests/cors.test.d.ts +0 -1
  189. package/dist/tests/cors.test.js +0 -55
  190. package/dist/tests/database.test.d.ts +0 -1
  191. package/dist/tests/database.test.js +0 -80
  192. package/dist/tests/dx.test.d.ts +0 -1
  193. package/dist/tests/dx.test.js +0 -114
  194. package/dist/tests/ecosystem.test.d.ts +0 -1
  195. package/dist/tests/ecosystem.test.js +0 -133
  196. package/dist/tests/features.test.d.ts +0 -1
  197. package/dist/tests/features.test.js +0 -47
  198. package/dist/tests/fusion.test.d.ts +0 -1
  199. package/dist/tests/fusion.test.js +0 -92
  200. package/dist/tests/http-basic.test.d.ts +0 -1
  201. package/dist/tests/http-basic.test.js +0 -124
  202. package/dist/tests/logger.test.d.ts +0 -1
  203. package/dist/tests/logger.test.js +0 -33
  204. package/dist/tests/middleware.test.d.ts +0 -1
  205. package/dist/tests/middleware.test.js +0 -109
  206. package/dist/tests/observability.test.d.ts +0 -1
  207. package/dist/tests/observability.test.js +0 -59
  208. package/dist/tests/openapi.test.d.ts +0 -1
  209. package/dist/tests/openapi.test.js +0 -64
  210. package/dist/tests/plugin.test.d.ts +0 -1
  211. package/dist/tests/plugin.test.js +0 -65
  212. package/dist/tests/plugins.test.d.ts +0 -1
  213. package/dist/tests/plugins.test.js +0 -71
  214. package/dist/tests/rate-limit.test.d.ts +0 -1
  215. package/dist/tests/rate-limit.test.js +0 -77
  216. package/dist/tests/resources.test.d.ts +0 -1
  217. package/dist/tests/resources.test.js +0 -47
  218. package/dist/tests/scheduler.test.d.ts +0 -1
  219. package/dist/tests/scheduler.test.js +0 -46
  220. package/dist/tests/schema-routes.test.d.ts +0 -1
  221. package/dist/tests/schema-routes.test.js +0 -79
  222. package/dist/tests/security.test.d.ts +0 -1
  223. package/dist/tests/security.test.js +0 -83
  224. package/dist/tests/server-db.test.d.ts +0 -1
  225. package/dist/tests/server-db.test.js +0 -72
  226. package/dist/tests/smoke.test.d.ts +0 -1
  227. package/dist/tests/smoke.test.js +0 -10
  228. package/dist/tests/sqlite-fusion.test.d.ts +0 -1
  229. package/dist/tests/sqlite-fusion.test.js +0 -92
  230. package/dist/tests/static.test.d.ts +0 -1
  231. package/dist/tests/static.test.js +0 -102
  232. package/dist/tests/stream.test.d.ts +0 -1
  233. package/dist/tests/stream.test.js +0 -44
  234. package/dist/tests/task-metrics.test.d.ts +0 -1
  235. package/dist/tests/task-metrics.test.js +0 -53
  236. package/dist/tests/tasks.test.d.ts +0 -1
  237. package/dist/tests/tasks.test.js +0 -62
  238. package/dist/tests/testing.test.d.ts +0 -1
  239. package/dist/tests/testing.test.js +0 -47
  240. package/dist/tests/validation.test.d.ts +0 -1
  241. package/dist/tests/validation.test.js +0 -107
  242. package/dist/tests/websocket.test.d.ts +0 -1
  243. package/dist/tests/websocket.test.js +0 -146
  244. package/dist/vitest.config.d.ts +0 -2
  245. package/dist/vitest.config.js +0 -9
  246. package/docs/AEGIS.md +0 -91
  247. package/docs/API_REFERENCE.md +0 -749
  248. package/docs/BENCHMARKS.md +0 -39
  249. package/docs/CAPABILITIES.md +0 -70
  250. package/docs/CLI.md +0 -43
  251. package/docs/DATABASE.md +0 -142
  252. package/docs/ECOSYSTEM.md +0 -146
  253. package/docs/ERRORS.md +0 -112
  254. package/docs/FUSION.md +0 -87
  255. package/docs/MIDDLEWARE.md +0 -65
  256. package/docs/MIGRATION_1.9_TO_2.0.md +0 -495
  257. package/docs/NEXT_STEPS.md +0 -99
  258. package/docs/OPENAPI.md +0 -99
  259. package/docs/PLUGINS.md +0 -59
  260. package/docs/PRODUCTION_DEPLOYMENT.md +0 -798
  261. package/docs/REAL_WORLD_EXAMPLES.md +0 -109
  262. package/docs/ROADMAP.md +0 -366
  263. package/docs/ROUTING.md +0 -78
  264. package/docs/SECURITY.md +0 -876
  265. package/docs/STATIC.md +0 -61
  266. package/docs/VALIDATION.md +0 -114
  267. package/docs/WEBSOCKETS.md +0 -76
package/dist/index.mjs ADDED
@@ -0,0 +1,1863 @@
1
+ import {
2
+ __require,
3
+ __toESM,
4
+ env,
5
+ loadEnv,
6
+ require_core
7
+ } from "./chunk-QW72SEAS.mjs";
8
+
9
+ // src/index.ts
10
+ var import_core = __toESM(require_core());
11
+
12
+ // src/context.ts
13
+ var Context = class {
14
+ constructor(engine, id, rawParams, queryString, rawBody, rawHeaders, rawUrl, responseHandle, method, serializer) {
15
+ this.engine = engine;
16
+ this.id = id;
17
+ this.rawParams = rawParams;
18
+ this.queryString = queryString;
19
+ this.rawBody = rawBody;
20
+ this.rawHeaders = rawHeaders;
21
+ this.responseHandle = responseHandle;
22
+ this.serializer = serializer;
23
+ this.method = method;
24
+ this.env = Object.entries(process.env).filter(([, value]) => value !== void 0).reduce((acc, [key, value]) => {
25
+ acc[key] = value;
26
+ return acc;
27
+ }, {});
28
+ this.perf = {
29
+ startTime: Date.now(),
30
+ dbDuration: 0,
31
+ parseDuration: 0,
32
+ allocations: 0
33
+ };
34
+ try {
35
+ this.url = new URL(rawUrl, "http://localhost");
36
+ } catch {
37
+ this.url = new URL("http://localhost");
38
+ }
39
+ this.path = this.url.pathname;
40
+ this.db = {
41
+ query: async (sql, ttl) => {
42
+ return this.engine.queryDb(sql, ttl);
43
+ },
44
+ queryWithParams: async (sql, params, ttl) => {
45
+ return this.engine.queryDbWithParams(sql, params, ttl);
46
+ },
47
+ mongo: (dbName, collName) => ({
48
+ find: async (query) => {
49
+ const json = await this.engine.queryMongo(dbName, collName, JSON.stringify(query));
50
+ return JSON.parse(json);
51
+ }
52
+ })
53
+ };
54
+ }
55
+ _status = 200;
56
+ _params = null;
57
+ _headers = null;
58
+ // Placeholder implementations for interface compliance
59
+ url;
60
+ method;
61
+ path;
62
+ body;
63
+ env;
64
+ db;
65
+ perf;
66
+ get params() {
67
+ if (!this._params) {
68
+ this._params = {};
69
+ for (let i = 0; i < this.rawParams.length; i += 2) {
70
+ this._params[this.rawParams[i]] = this.rawParams[i + 1];
71
+ }
72
+ }
73
+ return this._params;
74
+ }
75
+ _query = null;
76
+ get query() {
77
+ if (!this._query) {
78
+ this._query = {};
79
+ if (this.queryString) {
80
+ const searchParams = new URLSearchParams(this.queryString);
81
+ searchParams.forEach((value, key) => {
82
+ if (this._query[key]) {
83
+ if (Array.isArray(this._query[key])) {
84
+ this._query[key].push(value);
85
+ } else {
86
+ this._query[key] = [this._query[key], value];
87
+ }
88
+ } else {
89
+ this._query[key] = value;
90
+ }
91
+ });
92
+ }
93
+ }
94
+ return this._query;
95
+ }
96
+ get headers() {
97
+ if (!this._headers) {
98
+ this._headers = /* @__PURE__ */ new Map();
99
+ for (let i = 0; i < this.rawHeaders.length; i += 2) {
100
+ this._headers.set(this.rawHeaders[i].toLowerCase(), this.rawHeaders[i + 1]);
101
+ }
102
+ }
103
+ return this._headers;
104
+ }
105
+ // Body Methods (Getters)
106
+ text() {
107
+ return this.rawBody.toString("utf-8");
108
+ }
109
+ get req() {
110
+ return {
111
+ json: () => {
112
+ try {
113
+ return JSON.parse(this.text());
114
+ } catch (e) {
115
+ throw new Error(`Invalid JSON body: ${e}`);
116
+ }
117
+ },
118
+ text: () => this.text(),
119
+ param: (key) => this.params[key],
120
+ query: (key) => {
121
+ const val = this.query[key];
122
+ return Array.isArray(val) ? val[0] : val;
123
+ },
124
+ queries: (key) => {
125
+ const val = this.query[key];
126
+ return Array.isArray(val) ? val : [val];
127
+ },
128
+ header: (key) => this.headers.get(key.toLowerCase())
129
+ };
130
+ }
131
+ // Legacy support (Deprecated)
132
+ json(data, status) {
133
+ if (data !== void 0) {
134
+ if (status) this._status = status;
135
+ return this.send(data);
136
+ }
137
+ return this.req.json();
138
+ }
139
+ send(data) {
140
+ if (typeof data === "string") {
141
+ this.engine.sendResponse(this.responseHandle, this._status, data);
142
+ } else {
143
+ if (this.serializer) {
144
+ const body = this.serializer(data);
145
+ this.engine.sendResponse(this.responseHandle, this._status, body);
146
+ } else {
147
+ this.engine.sendResponse(this.responseHandle, this._status, JSON.stringify(data));
148
+ }
149
+ }
150
+ }
151
+ html(content) {
152
+ this.engine.sendHtml(this.responseHandle, this._status, content);
153
+ }
154
+ status(code) {
155
+ this._status = code;
156
+ return this;
157
+ }
158
+ get statusCode() {
159
+ return this._status;
160
+ }
161
+ snapshot() {
162
+ const normalizeValue = (value) => {
163
+ if (Array.isArray(value)) {
164
+ return [...value].map(normalizeValue).sort();
165
+ }
166
+ if (value && typeof value === "object") {
167
+ const keys = Object.keys(value).sort();
168
+ const sorted = {};
169
+ for (const key of keys) {
170
+ sorted[key] = normalizeValue(value[key]);
171
+ }
172
+ return sorted;
173
+ }
174
+ return value;
175
+ };
176
+ const headers = {};
177
+ for (const [key, value] of this.headers.entries()) {
178
+ headers[key] = value;
179
+ }
180
+ let body;
181
+ try {
182
+ body = this.req.json();
183
+ } catch {
184
+ body = this.text();
185
+ }
186
+ return {
187
+ id: this.id,
188
+ method: this.method,
189
+ url: this.url.toString(),
190
+ path: this.path,
191
+ params: normalizeValue(this.params),
192
+ query: normalizeValue(this.query),
193
+ headers: normalizeValue(headers),
194
+ body: normalizeValue(body),
195
+ env: normalizeValue(this.env),
196
+ perf: normalizeValue(this.perf)
197
+ };
198
+ }
199
+ };
200
+
201
+ // src/compose.ts
202
+ function compose(middleware) {
203
+ if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!");
204
+ for (const fn of middleware) {
205
+ if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!");
206
+ }
207
+ return function(context, next) {
208
+ let index = -1;
209
+ return dispatch(0);
210
+ function dispatch(i) {
211
+ if (i <= index) return Promise.reject(new Error("next() called multiple times"));
212
+ index = i;
213
+ let fn = middleware[i];
214
+ if (i === middleware.length) fn = next;
215
+ if (!fn) return Promise.resolve();
216
+ try {
217
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
218
+ } catch (err) {
219
+ return Promise.reject(err);
220
+ }
221
+ }
222
+ };
223
+ }
224
+
225
+ // src/index.ts
226
+ import fastJson from "fast-json-stringify";
227
+
228
+ // src/generator.ts
229
+ function generateClient(routes) {
230
+ const interfaces = [];
231
+ const paths = [];
232
+ let interfaceCounter = 0;
233
+ function schemaToTs(schema, name) {
234
+ if (!schema) return "any";
235
+ if (schema.type === "object") {
236
+ const props = [];
237
+ const required = new Set(schema.required || []);
238
+ for (const key in schema.properties) {
239
+ const propSchema = schema.properties[key];
240
+ const isReq = required.has(key);
241
+ const tsType = schemaToTs(propSchema, `${name}_${key}`);
242
+ props.push(` ${key}${isReq ? "" : "?"}: ${tsType};`);
243
+ }
244
+ const interfaceName = `I${name}`;
245
+ interfaces.push(`export interface ${interfaceName} {
246
+ ${props.join("\n")}
247
+ }`);
248
+ return interfaceName;
249
+ }
250
+ if (schema.type === "array") {
251
+ const itemType = schemaToTs(schema.items, `${name}_item`);
252
+ return `${itemType}[]`;
253
+ }
254
+ if (schema.enum) {
255
+ return schema.enum.map((v) => `'${v}'`).join(" | ");
256
+ }
257
+ if (schema.type === "string") return "string";
258
+ if (schema.type === "integer" || schema.type === "number") return "number";
259
+ if (schema.type === "boolean") return "boolean";
260
+ return "any";
261
+ }
262
+ const routeMap = {};
263
+ for (const route of routes) {
264
+ if (route.path.includes("/docs")) continue;
265
+ if (!routeMap[route.path]) routeMap[route.path] = {};
266
+ let reqType = "any";
267
+ if (route.options && route.options.schema) {
268
+ try {
269
+ const schema = JSON.parse(route.options.schema);
270
+ const name = `${route.method}_${route.path.replace(/[^a-zA-Z0-9]/g, "_")}_Body`;
271
+ reqType = schemaToTs(schema, name);
272
+ } catch (e) {
273
+ console.warn(`Failed to parse schema for client gen: ${route.path}`);
274
+ }
275
+ }
276
+ const resType = "any";
277
+ routeMap[route.path][route.method] = { req: reqType, res: resType };
278
+ }
279
+ const lines = [
280
+ `// Auto-generated QHTTPX Client`,
281
+ `// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
282
+ ``,
283
+ `export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';`,
284
+ ``,
285
+ `// --- Interfaces ---`,
286
+ ...interfaces,
287
+ ``,
288
+ `// --- Route Definitions ---`,
289
+ `export interface AppRoutes {`
290
+ ];
291
+ for (const [path, methods] of Object.entries(routeMap)) {
292
+ lines.push(` '${path}': {`);
293
+ for (const [method, types] of Object.entries(methods)) {
294
+ lines.push(` '${method}': {`);
295
+ lines.push(` request: ${types.req};`);
296
+ lines.push(` response: ${types.res};`);
297
+ lines.push(` };`);
298
+ }
299
+ lines.push(` };`);
300
+ }
301
+ lines.push(`}`);
302
+ lines.push(``);
303
+ lines.push(`
304
+ export class Client {
305
+ constructor(private baseUrl: string, private token?: string) {}
306
+
307
+ setToken(token: string) {
308
+ this.token = token;
309
+ }
310
+
311
+ private async request<P extends keyof AppRoutes, M extends keyof AppRoutes[P]>(
312
+ method: M,
313
+ path: P,
314
+ body?: any
315
+ ): Promise<any> {
316
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
317
+ if (this.token) headers['Authorization'] = \`Bearer \${this.token}\`;
318
+
319
+ // Handle path params if any (naive replacement)
320
+ // Note: The typed path must match the definition (e.g. /users/:id).
321
+ // Real usage would need a path builder, but for now we assume exact match or user string manipulation.
322
+
323
+ const res = await fetch(\`\${this.baseUrl}\${path as string}\`, {
324
+ method: method as string,
325
+ headers,
326
+ body: body ? JSON.stringify(body) : undefined
327
+ });
328
+
329
+ if (!res.ok) {
330
+ throw new Error(\`Request failed: \${res.status} \${res.statusText}\`);
331
+ }
332
+
333
+ const contentType = res.headers.get('content-type');
334
+ if (contentType && contentType.includes('application/json')) {
335
+ return res.json();
336
+ }
337
+ return res.text();
338
+ }
339
+
340
+ get<P extends keyof AppRoutes>(path: P): Promise<AppRoutes[P]['GET' & keyof AppRoutes[P]]['response']> {
341
+ return this.request('GET', path);
342
+ }
343
+
344
+ post<P extends keyof AppRoutes>(
345
+ path: P,
346
+ body: AppRoutes[P]['POST' & keyof AppRoutes[P]]['request']
347
+ ): Promise<AppRoutes[P]['POST' & keyof AppRoutes[P]]['response']> {
348
+ return this.request('POST', path, body);
349
+ }
350
+
351
+ put<P extends keyof AppRoutes>(
352
+ path: P,
353
+ body: AppRoutes[P]['PUT' & keyof AppRoutes[P]]['request']
354
+ ): Promise<AppRoutes[P]['PUT' & keyof AppRoutes[P]]['response']> {
355
+ return this.request('PUT', path, body);
356
+ }
357
+
358
+ delete<P extends keyof AppRoutes>(path: P): Promise<AppRoutes[P]['DELETE' & keyof AppRoutes[P]]['response']> {
359
+ return this.request('DELETE', path);
360
+ }
361
+ }
362
+ `);
363
+ return lines.join("\n");
364
+ }
365
+
366
+ // src/index.ts
367
+ import * as fs from "fs";
368
+
369
+ // src/test.ts
370
+ var TestClient = class {
371
+ app;
372
+ port;
373
+ baseUrl;
374
+ running = false;
375
+ constructor(app) {
376
+ this.app = app;
377
+ this.port = 0;
378
+ this.baseUrl = "";
379
+ }
380
+ async start() {
381
+ if (this.running) return;
382
+ this.port = Math.floor(Math.random() * 1e4) + 2e4;
383
+ this.baseUrl = `http://localhost:${this.port}`;
384
+ return new Promise((resolve, reject) => {
385
+ try {
386
+ this.app.listen(this.port, () => {
387
+ this.running = true;
388
+ resolve();
389
+ });
390
+ } catch (e) {
391
+ reject(e);
392
+ }
393
+ });
394
+ }
395
+ async stop() {
396
+ if (this.running) {
397
+ this.app.stop();
398
+ this.running = false;
399
+ }
400
+ }
401
+ async get(path, headers) {
402
+ const h = { "Connection": "close", ...headers };
403
+ return fetch(`${this.baseUrl}${path}`, { method: "GET", headers: h });
404
+ }
405
+ async post(path, body, headers) {
406
+ const isJson = typeof body === "object";
407
+ const h = { "Connection": "close", ...headers };
408
+ if (isJson && !h["Content-Type"]) h["Content-Type"] = "application/json";
409
+ return fetch(`${this.baseUrl}${path}`, {
410
+ method: "POST",
411
+ headers: h,
412
+ body: isJson ? JSON.stringify(body) : body
413
+ });
414
+ }
415
+ async put(path, body, headers) {
416
+ const isJson = typeof body === "object";
417
+ const h = { "Connection": "close", ...headers };
418
+ if (isJson && !h["Content-Type"]) h["Content-Type"] = "application/json";
419
+ return fetch(`${this.baseUrl}${path}`, {
420
+ method: "PUT",
421
+ headers: h,
422
+ body: isJson ? JSON.stringify(body) : body
423
+ });
424
+ }
425
+ async delete(path, headers) {
426
+ const h = { "Connection": "close", ...headers };
427
+ return fetch(`${this.baseUrl}${path}`, { method: "DELETE", headers: h });
428
+ }
429
+ };
430
+ function createTestClient(app) {
431
+ return new TestClient(app);
432
+ }
433
+
434
+ // src/fluent.ts
435
+ var requireOptional = (pkg) => {
436
+ try {
437
+ return __require(pkg);
438
+ } catch (e) {
439
+ throw new Error(`Dependency '${pkg}' is required for this feature. Please install it: npm install ${pkg}`);
440
+ }
441
+ };
442
+ var schemaFromValidation = (schema) => {
443
+ const properties = {};
444
+ const required = [];
445
+ for (const [key, type] of Object.entries(schema)) {
446
+ required.push(key);
447
+ if (type === "email") {
448
+ properties[key] = { type: "string", format: "email" };
449
+ } else if (type === "int" || type === "integer") {
450
+ properties[key] = { type: "integer" };
451
+ } else if (type === "bool" || type === "boolean") {
452
+ properties[key] = { type: "boolean" };
453
+ } else {
454
+ properties[key] = { type: "string" };
455
+ }
456
+ }
457
+ return { type: "object", properties, required };
458
+ };
459
+ var FluentBuilder = class {
460
+ constructor(app, method, path) {
461
+ this.app = app;
462
+ this.method = method;
463
+ this.path = path;
464
+ }
465
+ steps = [];
466
+ requestSchema;
467
+ responseSchemaDef;
468
+ queryBuilder;
469
+ // RouteBuilder compatibility methods
470
+ desc(description) {
471
+ this.steps.push({
472
+ name: "desc",
473
+ fn: (ctx, state) => {
474
+ return state;
475
+ }
476
+ });
477
+ return this;
478
+ }
479
+ auth(strategy) {
480
+ this.steps.push({
481
+ name: "auth",
482
+ fn: (ctx, state) => {
483
+ return state;
484
+ }
485
+ });
486
+ return this;
487
+ }
488
+ cache(options) {
489
+ this.steps.push({
490
+ name: "cache",
491
+ fn: (ctx, state) => {
492
+ return state;
493
+ }
494
+ });
495
+ return this;
496
+ }
497
+ rateLimit(options) {
498
+ this.steps.push({
499
+ name: "rateLimit",
500
+ fn: (ctx, state) => {
501
+ return state;
502
+ }
503
+ });
504
+ return this;
505
+ }
506
+ status(statusCode) {
507
+ this.steps.push({
508
+ name: "status",
509
+ fn: (ctx, state) => {
510
+ return { ...state, _status: statusCode };
511
+ }
512
+ });
513
+ return this;
514
+ }
515
+ priority(level) {
516
+ this.steps.push({
517
+ name: "priority",
518
+ fn: (ctx, state) => {
519
+ return state;
520
+ }
521
+ });
522
+ return this;
523
+ }
524
+ slo(targetMs) {
525
+ this.steps.push({
526
+ name: "slo",
527
+ fn: (ctx, state) => {
528
+ return state;
529
+ }
530
+ });
531
+ return this;
532
+ }
533
+ // 1. Validation Step
534
+ validate(schema) {
535
+ if (!this.requestSchema) {
536
+ this.requestSchema = schemaFromValidation(schema);
537
+ }
538
+ this.steps.push({
539
+ name: "validate",
540
+ fn: (ctx, state) => {
541
+ const body = ctx.req.json();
542
+ const errors = [];
543
+ const cleanData = {};
544
+ for (const [key, type] of Object.entries(schema)) {
545
+ if (!body[key]) {
546
+ errors.push(`Missing ${key}`);
547
+ continue;
548
+ }
549
+ if (type === "email" && !body[key].includes("@")) {
550
+ errors.push(`Invalid email for ${key}`);
551
+ }
552
+ cleanData[key] = body[key];
553
+ }
554
+ if (errors.length > 0) {
555
+ throw { status: 400, message: errors.join(", ") };
556
+ }
557
+ return { ...state, ...cleanData };
558
+ }
559
+ });
560
+ return this;
561
+ }
562
+ schema(def) {
563
+ this.requestSchema = def;
564
+ return this;
565
+ }
566
+ responseSchema(def) {
567
+ this.responseSchemaDef = def;
568
+ return this;
569
+ }
570
+ query(schemaBuilder) {
571
+ this.queryBuilder = schemaBuilder;
572
+ return this;
573
+ }
574
+ queryState(fields) {
575
+ this.steps.push({
576
+ name: "queryState",
577
+ fn: (ctx, state) => {
578
+ const query = ctx.query;
579
+ if (!fields || fields.length === 0) {
580
+ return { ...state, ...query };
581
+ }
582
+ const next = { ...state };
583
+ for (const field of fields) {
584
+ if (query[field] !== void 0) {
585
+ next[field] = query[field];
586
+ }
587
+ }
588
+ return next;
589
+ }
590
+ });
591
+ return this;
592
+ }
593
+ use(fn) {
594
+ this.steps.push({
595
+ name: "use",
596
+ fn
597
+ });
598
+ return this;
599
+ }
600
+ sql(query, params) {
601
+ this.steps.push({
602
+ name: "sql",
603
+ fn: async (ctx, state) => {
604
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
605
+ const resolvedParams = typeof params === "function" ? params(state) : params;
606
+ const res = resolvedParams ? await ctx.db.queryWithParams(query, resolvedParams) : await ctx.db.query(query);
607
+ return JSON.parse(res);
608
+ }
609
+ });
610
+ return this;
611
+ }
612
+ // 2. Transformation Step (e.g., Hashing)
613
+ transform(fn) {
614
+ this.steps.push({
615
+ name: "transform",
616
+ fn: async (ctx, state) => {
617
+ const result = await fn(state);
618
+ return { ...state, ...result };
619
+ }
620
+ });
621
+ return this;
622
+ }
623
+ // Pre-built Transform: Hash Password
624
+ hash(field = "password") {
625
+ return this.transform(async (data) => {
626
+ const bcrypt = requireOptional("bcryptjs");
627
+ if (data[field]) {
628
+ const hash = await bcrypt.hash(data[field], 10);
629
+ return { [field]: hash };
630
+ }
631
+ return {};
632
+ });
633
+ }
634
+ // 3. Database Step (Insert)
635
+ insert(table) {
636
+ this.steps.push({
637
+ name: `insert:${table}`,
638
+ fn: async (ctx, state) => {
639
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
640
+ const keys = Object.keys(state);
641
+ const values = Object.values(state);
642
+ const placeholders = values.map((_, i) => `$${i + 1}`);
643
+ const sql = `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING *`;
644
+ try {
645
+ const res = await ctx.db.queryWithParams(sql, values);
646
+ const rows = JSON.parse(res);
647
+ return rows[0];
648
+ } catch (e) {
649
+ if (e.message?.includes("duplicate")) {
650
+ throw { status: 409, message: "Resource already exists" };
651
+ }
652
+ throw e;
653
+ }
654
+ }
655
+ });
656
+ return this;
657
+ }
658
+ // Database Select
659
+ find(table, by) {
660
+ this.steps.push({
661
+ name: `find:${table}`,
662
+ fn: async (ctx, state) => {
663
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
664
+ const val = state[by] || ctx.req.json()[by];
665
+ if (!val) throw { status: 400, message: `Missing lookup value for ${by}` };
666
+ const fields = state._query_select ? state._query_select.join(", ") : "*";
667
+ const sql = `SELECT ${fields} FROM ${table} WHERE ${by} = $1`;
668
+ const res = await ctx.db.queryWithParams(sql, [val]);
669
+ const rows = JSON.parse(res);
670
+ if (rows.length === 0) return null;
671
+ return rows[0];
672
+ }
673
+ });
674
+ return this;
675
+ }
676
+ // 4. Logic/Guard Step
677
+ ensure(condition, errorMsg = "Condition failed", status = 400) {
678
+ this.steps.push({
679
+ name: "ensure",
680
+ fn: (ctx, state) => {
681
+ if (!condition(state)) {
682
+ throw { status, message: errorMsg };
683
+ }
684
+ return state;
685
+ }
686
+ });
687
+ return this;
688
+ }
689
+ verifyPassword(field = "password") {
690
+ this.steps.push({
691
+ name: "verifyPassword",
692
+ fn: async (ctx, state) => {
693
+ const bcrypt = requireOptional("bcryptjs");
694
+ const body = ctx.req.json();
695
+ const inputPass = body[field];
696
+ const storedHash = state[field] || state[`${field}_hash`];
697
+ if (!inputPass || !storedHash) {
698
+ throw { status: 400, message: "Missing credentials" };
699
+ }
700
+ const valid = await bcrypt.compare(inputPass, storedHash);
701
+ if (!valid) throw { status: 401, message: "Invalid credentials" };
702
+ return state;
703
+ }
704
+ });
705
+ return this;
706
+ }
707
+ // 5. Auth Step (JWT)
708
+ jwt(options = {}) {
709
+ this.steps.push({
710
+ name: "jwt",
711
+ fn: (ctx, state) => {
712
+ const jwt = requireOptional("jsonwebtoken");
713
+ const secret = options.secret || process.env.JWT_SECRET || "secret";
714
+ const payload = { ...state };
715
+ delete payload.password;
716
+ delete payload.password_hash;
717
+ if (payload.id) payload.sub = payload.id;
718
+ const token = jwt.sign(payload, secret, { expiresIn: options.expiresIn || "24h" });
719
+ return { token, user: payload };
720
+ }
721
+ });
722
+ return this;
723
+ }
724
+ // 6. Response Step (Finalizer)
725
+ respond(handlerOrStatus = 200) {
726
+ if (typeof handlerOrStatus === "function") {
727
+ const fn = handlerOrStatus;
728
+ this.use(async (ctx, state) => await fn(ctx));
729
+ this.respond(200);
730
+ return;
731
+ }
732
+ if (typeof handlerOrStatus === "object" && handlerOrStatus !== null) {
733
+ const route2 = this.app[this.method.toLowerCase()](this.path, handlerOrStatus);
734
+ this.applyOptionsToRoute(route2);
735
+ return;
736
+ }
737
+ const status = handlerOrStatus;
738
+ const handler = async (ctx) => {
739
+ let state = {};
740
+ let finalStatus = status;
741
+ try {
742
+ for (const step of this.steps) {
743
+ const result = await step.fn(ctx, state);
744
+ if (result !== void 0) {
745
+ state = result;
746
+ }
747
+ if (state._status !== void 0) {
748
+ finalStatus = state._status;
749
+ delete state._status;
750
+ }
751
+ }
752
+ ctx.json(state, finalStatus);
753
+ } catch (e) {
754
+ const status2 = e.status || 500;
755
+ const message = e.message || "Internal Server Error";
756
+ console.error(`[Fluent] Error in ${this.method} ${this.path}:`, e);
757
+ ctx.json({ error: message }, status2);
758
+ }
759
+ };
760
+ const route = this.app[this.method.toLowerCase()](this.path, handler);
761
+ this.applyOptionsToRoute(route);
762
+ }
763
+ applyOptionsToRoute(route) {
764
+ if (this.requestSchema) {
765
+ route.schema(this.requestSchema);
766
+ }
767
+ if (this.responseSchemaDef) {
768
+ route.responseSchema(this.responseSchemaDef);
769
+ }
770
+ if (this.queryBuilder) {
771
+ route.query(this.queryBuilder);
772
+ }
773
+ if (this.useJwt) {
774
+ route.jwt();
775
+ }
776
+ }
777
+ useJwt = false;
778
+ // Route Options
779
+ secure() {
780
+ this.useJwt = true;
781
+ this.steps.unshift({
782
+ name: "auth:extract",
783
+ fn: (ctx, state) => {
784
+ const authHeader = ctx.req.header("authorization");
785
+ if (authHeader) {
786
+ const token = authHeader.split(" ")[1];
787
+ try {
788
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
789
+ return { user: payload };
790
+ } catch (e) {
791
+ }
792
+ }
793
+ return state;
794
+ }
795
+ });
796
+ return this;
797
+ }
798
+ // Query Configuration Steps
799
+ select(fields) {
800
+ this.steps.push({
801
+ name: "select",
802
+ fn: (ctx, state) => ({ ...state, _query_select: fields })
803
+ });
804
+ return this;
805
+ }
806
+ sort(field, direction = "ASC") {
807
+ this.steps.push({
808
+ name: "sort",
809
+ fn: (ctx, state) => ({ ...state, _query_sort: { field, direction } })
810
+ });
811
+ return this;
812
+ }
813
+ paginate(options) {
814
+ this.steps.push({
815
+ name: "paginate",
816
+ fn: (ctx, state) => ({ ...state, _query_paginate: options })
817
+ });
818
+ return this;
819
+ }
820
+ autoFilter(table, allow, options = {}) {
821
+ const sortAllow = options.sort || allow;
822
+ const selectAllow = options.select || allow;
823
+ const defaultSort = options.defaultSort || "id";
824
+ const defaultSortSafe = sortAllow.length > 0 ? sortAllow.includes(defaultSort) ? defaultSort : sortAllow[0] : defaultSort;
825
+ const defaultDirection = options.defaultDirection || "DESC";
826
+ const maxLimit = options.maxLimit || 100;
827
+ const pageParam = options.pageParam || "page";
828
+ const limitParam = options.limitParam || "limit";
829
+ const sortParam = options.sortParam || "sort";
830
+ const fieldsParam = options.fieldsParam || "fields";
831
+ const existingQueryBuilder = this.queryBuilder;
832
+ this.queryBuilder = (q) => {
833
+ if (existingQueryBuilder) existingQueryBuilder(q);
834
+ for (const field of allow) {
835
+ q.string(field).optional();
836
+ }
837
+ q.int(pageParam).optional().min(1);
838
+ q.int(limitParam).optional().min(1).max(maxLimit);
839
+ q.string(sortParam).optional();
840
+ q.string(fieldsParam).optional();
841
+ };
842
+ this.steps.push({
843
+ name: `autoFilter:${table}`,
844
+ fn: async (ctx, state) => {
845
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
846
+ const query = ctx.query;
847
+ const filters = {};
848
+ for (const field of allow) {
849
+ if (query[field] !== void 0) {
850
+ filters[field] = query[field];
851
+ }
852
+ }
853
+ let selectFields = "*";
854
+ const fieldsRaw = query[fieldsParam];
855
+ if (typeof fieldsRaw === "string") {
856
+ const parts = fieldsRaw.split(",").map((item) => item.trim()).filter((item) => selectAllow.includes(item));
857
+ if (parts.length > 0) {
858
+ selectFields = parts.join(", ");
859
+ }
860
+ }
861
+ let sortField = defaultSortSafe;
862
+ let sortDirection = defaultDirection;
863
+ const sortRaw = typeof query[sortParam] === "string" ? query[sortParam] : void 0;
864
+ if (sortRaw) {
865
+ let field = sortRaw;
866
+ let direction = defaultDirection;
867
+ if (sortRaw.includes(":")) {
868
+ const [f, d] = sortRaw.split(":");
869
+ field = f;
870
+ direction = d?.toLowerCase() === "desc" ? "DESC" : "ASC";
871
+ } else if (sortRaw.endsWith("_desc")) {
872
+ field = sortRaw.replace("_desc", "");
873
+ direction = "DESC";
874
+ } else if (sortRaw.endsWith("_asc")) {
875
+ field = sortRaw.replace("_asc", "");
876
+ direction = "ASC";
877
+ }
878
+ if (sortAllow.includes(field)) {
879
+ sortField = field;
880
+ sortDirection = direction;
881
+ }
882
+ }
883
+ let limit;
884
+ if (typeof query[limitParam] === "number") {
885
+ limit = query[limitParam];
886
+ } else if (typeof query[limitParam] === "string") {
887
+ const parsed = Number.parseInt(query[limitParam], 10);
888
+ if (!Number.isNaN(parsed)) limit = parsed;
889
+ }
890
+ if (limit !== void 0) {
891
+ limit = Math.min(Math.max(limit, 1), maxLimit);
892
+ }
893
+ let page = typeof query[pageParam] === "number" ? query[pageParam] : 1;
894
+ if (typeof query[pageParam] === "string") {
895
+ const parsed = Number.parseInt(query[pageParam], 10);
896
+ if (!Number.isNaN(parsed)) page = parsed;
897
+ }
898
+ if (!page || page < 1) page = 1;
899
+ let sql = `SELECT ${selectFields} FROM ${table}`;
900
+ const conditions = [];
901
+ const params = [];
902
+ for (const [key, value] of Object.entries(filters)) {
903
+ if (Array.isArray(value)) {
904
+ const offset = params.length;
905
+ for (const item of value) {
906
+ params.push(item);
907
+ }
908
+ const placeholdersFixed = value.map((_, idx) => `$${offset + idx + 1}`).join(", ");
909
+ conditions.push(`${key} IN (${placeholdersFixed})`);
910
+ } else {
911
+ params.push(value);
912
+ conditions.push(`${key} = $${params.length}`);
913
+ }
914
+ }
915
+ if (conditions.length > 0) {
916
+ sql += ` WHERE ${conditions.join(" AND ")}`;
917
+ }
918
+ if (sortField) {
919
+ sql += ` ORDER BY ${sortField} ${sortDirection}`;
920
+ }
921
+ if (limit !== void 0) {
922
+ params.push(limit);
923
+ sql += ` LIMIT $${params.length}`;
924
+ }
925
+ if (limit !== void 0) {
926
+ const offset = (page - 1) * limit;
927
+ params.push(offset);
928
+ sql += ` OFFSET $${params.length}`;
929
+ }
930
+ const res = await ctx.db.queryWithParams(sql, params);
931
+ const rows = JSON.parse(res);
932
+ return {
933
+ data: rows,
934
+ meta: {
935
+ page,
936
+ limit,
937
+ sort: { field: sortField, direction: sortDirection },
938
+ filters
939
+ }
940
+ };
941
+ }
942
+ });
943
+ return this;
944
+ }
945
+ // Database List (Select with filter)
946
+ list(table, options = {}) {
947
+ this.steps.push({
948
+ name: `list:${table}`,
949
+ fn: async (ctx, state) => {
950
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
951
+ const fields = state._query_select ? state._query_select.join(", ") : "*";
952
+ let sql = `SELECT ${fields} FROM ${table}`;
953
+ const conditions = [];
954
+ const params = [];
955
+ if (options.where) {
956
+ for (const [key, valRef] of Object.entries(options.where)) {
957
+ let val;
958
+ if (valRef.startsWith("@")) {
959
+ const path = valRef.substring(1).split(".");
960
+ val = path.reduce((acc, part) => acc && acc[part], state);
961
+ } else {
962
+ val = valRef;
963
+ }
964
+ if (val === void 0) continue;
965
+ conditions.push(`${key} = $${params.length + 1}`);
966
+ params.push(val);
967
+ }
968
+ }
969
+ if (conditions.length > 0) {
970
+ sql += ` WHERE ${conditions.join(" AND ")}`;
971
+ }
972
+ if (state._query_sort) {
973
+ sql += ` ORDER BY ${state._query_sort.field} ${state._query_sort.direction}`;
974
+ } else {
975
+ sql += " ORDER BY id DESC";
976
+ }
977
+ const limit = options.limit || state._query_paginate?.limit;
978
+ if (limit) {
979
+ sql += ` LIMIT ${limit}`;
980
+ }
981
+ if (state._query_paginate?.page && limit) {
982
+ const offset = (state._query_paginate.page - 1) * limit;
983
+ sql += ` OFFSET ${offset}`;
984
+ }
985
+ const res = await ctx.db.queryWithParams(sql, params);
986
+ return JSON.parse(res);
987
+ }
988
+ });
989
+ return this;
990
+ }
991
+ // Database Update
992
+ update(table, options) {
993
+ this.steps.push({
994
+ name: `update:${table}`,
995
+ fn: async (ctx, state) => {
996
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
997
+ const body = ctx.req.json();
998
+ const params = [];
999
+ const updates = [];
1000
+ for (const [key, val] of Object.entries(body)) {
1001
+ if (key === "id") continue;
1002
+ if (options.fields && !options.fields.includes(key)) continue;
1003
+ updates.push(`${key} = $${params.length + 1}`);
1004
+ params.push(val);
1005
+ }
1006
+ if (updates.length === 0) return state;
1007
+ const conditions = [];
1008
+ for (const [key, valRef] of Object.entries(options.where)) {
1009
+ let val;
1010
+ if (valRef.startsWith("@")) {
1011
+ const path = valRef.substring(1).split(".");
1012
+ val = path.reduce((acc, part) => acc && acc[part], state);
1013
+ } else if (valRef.startsWith(":")) {
1014
+ val = ctx.req.param(valRef.substring(1));
1015
+ } else {
1016
+ val = valRef;
1017
+ }
1018
+ if (val === void 0) throw { status: 400, message: `Missing value for ${key}` };
1019
+ conditions.push(`${key} = $${params.length + 1}`);
1020
+ params.push(val);
1021
+ }
1022
+ const sql = `UPDATE ${table} SET ${updates.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`;
1023
+ const res = await ctx.db.queryWithParams(sql, params);
1024
+ const rows = JSON.parse(res);
1025
+ if (rows.length === 0) throw { status: 404, message: "Resource not found" };
1026
+ return rows[0];
1027
+ }
1028
+ });
1029
+ return this;
1030
+ }
1031
+ // Database Soft Delete
1032
+ softDelete(table, options) {
1033
+ this.steps.push({
1034
+ name: `softDelete:${table}`,
1035
+ fn: async (ctx, state) => {
1036
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
1037
+ const conditions = [];
1038
+ const params = [];
1039
+ for (const [key, valRef] of Object.entries(options.where)) {
1040
+ let val;
1041
+ if (valRef.startsWith("@")) {
1042
+ const path = valRef.substring(1).split(".");
1043
+ val = path.reduce((acc, part) => acc && acc[part], state);
1044
+ } else if (valRef.startsWith(":")) {
1045
+ val = ctx.req.param(valRef.substring(1));
1046
+ } else {
1047
+ val = valRef;
1048
+ }
1049
+ if (val === void 0) throw { status: 400, message: `Missing value for ${key}` };
1050
+ conditions.push(`${key} = $${params.length + 1}`);
1051
+ params.push(val);
1052
+ }
1053
+ const sql = `UPDATE ${table} SET deleted_at = NOW() WHERE ${conditions.join(" AND ")} RETURNING *`;
1054
+ const res = await ctx.db.queryWithParams(sql, params);
1055
+ const rows = JSON.parse(res);
1056
+ if (rows.length === 0) throw { status: 404, message: "Resource not found" };
1057
+ return { success: true, deleted: rows[0] };
1058
+ }
1059
+ });
1060
+ return this;
1061
+ }
1062
+ // Database Delete
1063
+ delete(table, options) {
1064
+ this.steps.push({
1065
+ name: `delete:${table}`,
1066
+ fn: async (ctx, state) => {
1067
+ if (!ctx.db) throw { status: 500, message: "Database not connected" };
1068
+ const conditions = [];
1069
+ const params = [];
1070
+ for (const [key, valRef] of Object.entries(options.where)) {
1071
+ let val;
1072
+ if (valRef.startsWith("@")) {
1073
+ const path = valRef.substring(1).split(".");
1074
+ val = path.reduce((acc, part) => acc && acc[part], state);
1075
+ } else if (valRef.startsWith(":")) {
1076
+ val = ctx.req.param(valRef.substring(1));
1077
+ } else {
1078
+ val = valRef;
1079
+ }
1080
+ if (val === void 0) throw { status: 400, message: `Missing value for ${key}` };
1081
+ conditions.push(`${key} = $${params.length + 1}`);
1082
+ params.push(val);
1083
+ }
1084
+ const sql = `DELETE FROM ${table} WHERE ${conditions.join(" AND ")} RETURNING *`;
1085
+ const res = await ctx.db.queryWithParams(sql, params);
1086
+ const rows = JSON.parse(res);
1087
+ if (rows.length === 0) throw { status: 404, message: "Resource not found" };
1088
+ return { success: true, deleted: rows[0] };
1089
+ }
1090
+ });
1091
+ return this;
1092
+ }
1093
+ };
1094
+
1095
+ // src/index.ts
1096
+ loadEnv();
1097
+ var Q = class {
1098
+ static env = env;
1099
+ static loadEnv = loadEnv;
1100
+ static app(config) {
1101
+ return new App(config);
1102
+ }
1103
+ static schema(def) {
1104
+ const properties = {};
1105
+ for (const key in def) {
1106
+ if (def[key] && typeof def[key].toJSON === "function") {
1107
+ properties[key] = def[key].toJSON();
1108
+ } else {
1109
+ properties[key] = def[key];
1110
+ }
1111
+ }
1112
+ return {
1113
+ type: "object",
1114
+ properties,
1115
+ required: Object.keys(properties)
1116
+ };
1117
+ }
1118
+ static string() {
1119
+ return new SchemaBuilder("string");
1120
+ }
1121
+ static int() {
1122
+ return new SchemaBuilder("integer");
1123
+ }
1124
+ static email() {
1125
+ return new SchemaBuilder("string").format("email");
1126
+ }
1127
+ static enum(...values) {
1128
+ return new SchemaBuilder("string").enum(values);
1129
+ }
1130
+ };
1131
+ var SchemaBuilder = class {
1132
+ def = {};
1133
+ constructor(type) {
1134
+ this.def.type = type;
1135
+ }
1136
+ min(val) {
1137
+ if (this.def.type === "string") this.def.minLength = val;
1138
+ else this.def.minimum = val;
1139
+ return this;
1140
+ }
1141
+ max(val) {
1142
+ if (this.def.type === "string") this.def.maxLength = val;
1143
+ else this.def.maximum = val;
1144
+ return this;
1145
+ }
1146
+ format(fmt) {
1147
+ this.def.format = fmt;
1148
+ return this;
1149
+ }
1150
+ enum(values) {
1151
+ this.def.enum = values;
1152
+ return this;
1153
+ }
1154
+ toJSON() {
1155
+ return this.def;
1156
+ }
1157
+ };
1158
+ var QuerySchemaBuilder = class {
1159
+ schema = {};
1160
+ string(name) {
1161
+ return new QueryFieldBuilder(this.schema, name, "string");
1162
+ }
1163
+ int(name) {
1164
+ return new QueryFieldBuilder(this.schema, name, "integer");
1165
+ }
1166
+ bool(name) {
1167
+ return new QueryFieldBuilder(this.schema, name, "boolean");
1168
+ }
1169
+ };
1170
+ var QueryFieldBuilder = class {
1171
+ constructor(schema, name, type) {
1172
+ this.schema = schema;
1173
+ this.name = name;
1174
+ this.type = type;
1175
+ if (!this.schema[this.name]) {
1176
+ this.schema[this.name] = { type: this.type };
1177
+ }
1178
+ }
1179
+ default(val) {
1180
+ this.schema[this.name].default = val;
1181
+ return this;
1182
+ }
1183
+ optional() {
1184
+ this.schema[this.name].optional = true;
1185
+ return this;
1186
+ }
1187
+ max(val) {
1188
+ this.schema[this.name].max = val;
1189
+ return this;
1190
+ }
1191
+ min(val) {
1192
+ this.schema[this.name].min = val;
1193
+ return this;
1194
+ }
1195
+ };
1196
+ var normalizeQuery = (schema, ctx) => {
1197
+ const errors = [];
1198
+ const normalized = { ...ctx.query };
1199
+ const parseValue = (value, field) => {
1200
+ if (field.type === "integer") {
1201
+ const parsed = Number.parseInt(value, 10);
1202
+ if (Number.isNaN(parsed)) {
1203
+ return { ok: false, value };
1204
+ }
1205
+ return { ok: true, value: parsed };
1206
+ }
1207
+ if (field.type === "boolean") {
1208
+ if (value === "true" || value === "1") return { ok: true, value: true };
1209
+ if (value === "false" || value === "0") return { ok: true, value: false };
1210
+ return { ok: false, value };
1211
+ }
1212
+ return { ok: true, value };
1213
+ };
1214
+ const enforceBounds = (value, field) => {
1215
+ if (typeof value === "string") {
1216
+ if (field.min !== void 0 && value.length < field.min) return false;
1217
+ if (field.max !== void 0 && value.length > field.max) return false;
1218
+ }
1219
+ if (typeof value === "number") {
1220
+ if (field.min !== void 0 && value < field.min) return false;
1221
+ if (field.max !== void 0 && value > field.max) return false;
1222
+ }
1223
+ return true;
1224
+ };
1225
+ for (const [key, field] of Object.entries(schema)) {
1226
+ const raw = ctx.query[key];
1227
+ if (raw === void 0) {
1228
+ if (field.default !== void 0) {
1229
+ normalized[key] = field.default;
1230
+ continue;
1231
+ }
1232
+ if (field.optional) {
1233
+ delete normalized[key];
1234
+ continue;
1235
+ }
1236
+ errors.push(`Missing query param: ${key}`);
1237
+ continue;
1238
+ }
1239
+ if (Array.isArray(raw)) {
1240
+ const parsedValues = [];
1241
+ let ok = true;
1242
+ for (const item of raw) {
1243
+ const parsed2 = parseValue(item, field);
1244
+ if (!parsed2.ok) {
1245
+ ok = false;
1246
+ break;
1247
+ }
1248
+ if (!enforceBounds(parsed2.value, field)) {
1249
+ ok = false;
1250
+ break;
1251
+ }
1252
+ parsedValues.push(parsed2.value);
1253
+ }
1254
+ if (!ok) {
1255
+ errors.push(`Invalid query param: ${key}`);
1256
+ continue;
1257
+ }
1258
+ normalized[key] = parsedValues;
1259
+ continue;
1260
+ }
1261
+ const parsed = parseValue(raw, field);
1262
+ if (!parsed.ok || !enforceBounds(parsed.value, field)) {
1263
+ errors.push(`Invalid query param: ${key}`);
1264
+ continue;
1265
+ }
1266
+ normalized[key] = parsed.value;
1267
+ }
1268
+ if (errors.length > 0) {
1269
+ return { ok: false, errors };
1270
+ }
1271
+ return { ok: true, query: normalized };
1272
+ };
1273
+ var normalizeRoutePath = (path) => {
1274
+ return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}");
1275
+ };
1276
+ var App = class {
1277
+ constructor(config) {
1278
+ this.config = config;
1279
+ this.db = {
1280
+ connectPostgres: async (url) => {
1281
+ if (!this.engine) throw new Error("Server not started");
1282
+ await this.engine.connectPostgres(url);
1283
+ },
1284
+ connectSqlite: async (url) => {
1285
+ if (!this.engine) throw new Error("Server not started");
1286
+ await this.engine.connectSqlite(url);
1287
+ },
1288
+ connectRedis: (url) => {
1289
+ if (!this.engine) throw new Error("Server not started");
1290
+ this.engine.connectRedis(url);
1291
+ },
1292
+ connectMongo: async (url) => {
1293
+ if (!this.engine) throw new Error("Server not started");
1294
+ await this.engine.connectMongo(url);
1295
+ },
1296
+ query: async (sql, ttl) => {
1297
+ if (!this.engine) throw new Error("Server not started");
1298
+ return this.engine.queryDb(sql, ttl);
1299
+ },
1300
+ redis: {
1301
+ set: async (key, value, ttl) => {
1302
+ if (!this.engine) throw new Error("Server not started");
1303
+ await this.engine.redisSet(key, value, ttl);
1304
+ },
1305
+ get: async (key) => {
1306
+ if (!this.engine) throw new Error("Server not started");
1307
+ return this.engine.redisGet(key);
1308
+ }
1309
+ },
1310
+ mongo: (dbName, collName) => ({
1311
+ find: async (query) => {
1312
+ if (!this.engine) throw new Error("Server not started");
1313
+ const json = await this.engine.queryMongo(dbName, collName, JSON.stringify(query));
1314
+ return JSON.parse(json);
1315
+ }
1316
+ })
1317
+ };
1318
+ this.auth = {
1319
+ setJwtSecret: (secret) => {
1320
+ if (!this.engine) throw new Error("Server not started");
1321
+ this.engine.setJwtSecret(secret);
1322
+ }
1323
+ };
1324
+ }
1325
+ engine = null;
1326
+ routes = [];
1327
+ handlers = /* @__PURE__ */ new Map();
1328
+ handlerCounter = 0;
1329
+ middlewares = [];
1330
+ staticRoutes = /* @__PURE__ */ new Map();
1331
+ // prefix -> dir
1332
+ wsHandlers = /* @__PURE__ */ new Map();
1333
+ activeSockets = /* @__PURE__ */ new Map();
1334
+ corsConfig = null;
1335
+ loggingEnabled = false;
1336
+ errorHandler = null;
1337
+ errorHooksRegistered = false;
1338
+ shutdownHooksRegistered = false;
1339
+ // Fluent API Entry Point
1340
+ flow(method, path) {
1341
+ return new FluentBuilder(this, method, path);
1342
+ }
1343
+ doc(path) {
1344
+ this.get(`${path}/json`, (req) => {
1345
+ const spec = this.generateOpenApiSpec();
1346
+ return req.send(spec);
1347
+ });
1348
+ this.get(path, (req) => {
1349
+ const html = `
1350
+ <!DOCTYPE html>
1351
+ <html lang="en">
1352
+ <head>
1353
+ <meta charset="utf-8" />
1354
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1355
+ <title>API Documentation</title>
1356
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
1357
+ </head>
1358
+ <body>
1359
+ <div id="swagger-ui"></div>
1360
+ <script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js" crossorigin></script>
1361
+ <script>
1362
+ window.onload = () => {
1363
+ window.ui = SwaggerUIBundle({
1364
+ url: '${path}/json',
1365
+ dom_id: '#swagger-ui',
1366
+ });
1367
+ };
1368
+ </script>
1369
+ </body>
1370
+ </html>`;
1371
+ return req.html(html);
1372
+ });
1373
+ return this;
1374
+ }
1375
+ exportClient(outputPath) {
1376
+ const code = generateClient(this.routes);
1377
+ fs.writeFileSync(outputPath, code);
1378
+ console.log(`\u2705 Generated Type-Safe Client at ${outputPath}`);
1379
+ }
1380
+ generateOpenApiSpec() {
1381
+ const paths = {};
1382
+ for (const route of this.routes) {
1383
+ if (route.path.includes("/docs")) continue;
1384
+ const specPath = normalizeRoutePath(route.path);
1385
+ if (!paths[specPath]) paths[specPath] = {};
1386
+ const method = route.method.toLowerCase();
1387
+ const operation = {
1388
+ responses: {
1389
+ "200": { description: "Successful response" }
1390
+ }
1391
+ };
1392
+ if (route.description) {
1393
+ operation.summary = route.description;
1394
+ }
1395
+ if (route.options.schema) {
1396
+ try {
1397
+ const schema = typeof route.options.schema === "string" ? JSON.parse(route.options.schema) : route.options.schema;
1398
+ operation.requestBody = {
1399
+ content: {
1400
+ "application/json": {
1401
+ schema
1402
+ }
1403
+ }
1404
+ };
1405
+ } catch (e) {
1406
+ console.warn(`Failed to parse schema for ${method} ${route.path}`);
1407
+ }
1408
+ }
1409
+ if (route.options.query_schema) {
1410
+ const params = [];
1411
+ const querySchema = route.options.query_schema;
1412
+ for (const [name, def] of Object.entries(querySchema)) {
1413
+ params.push({
1414
+ name,
1415
+ in: "query",
1416
+ required: !def.optional && def.default === void 0,
1417
+ schema: {
1418
+ type: def.type === "integer" ? "integer" : def.type === "boolean" ? "boolean" : "string",
1419
+ minimum: def.min,
1420
+ maximum: def.max
1421
+ }
1422
+ });
1423
+ }
1424
+ if (params.length > 0) {
1425
+ operation.parameters = params;
1426
+ }
1427
+ }
1428
+ if (route.options.response_schema) {
1429
+ try {
1430
+ const schema = typeof route.options.response_schema === "string" ? JSON.parse(route.options.response_schema) : route.options.response_schema;
1431
+ operation.responses["200"] = {
1432
+ description: "Successful response",
1433
+ content: {
1434
+ "application/json": { schema }
1435
+ }
1436
+ };
1437
+ } catch (e) {
1438
+ console.warn(`Failed to parse response schema for ${method} ${route.path}`);
1439
+ }
1440
+ }
1441
+ if (route.options.jwt_auth) {
1442
+ operation.security = [{ bearerAuth: [] }];
1443
+ }
1444
+ paths[specPath][method] = operation;
1445
+ }
1446
+ return {
1447
+ openapi: "3.0.0",
1448
+ info: {
1449
+ title: "QHTTPX API",
1450
+ version: "1.0.0"
1451
+ },
1452
+ components: {
1453
+ securitySchemes: {
1454
+ bearerAuth: {
1455
+ type: "http",
1456
+ scheme: "bearer",
1457
+ bearerFormat: "JWT"
1458
+ }
1459
+ }
1460
+ },
1461
+ paths
1462
+ };
1463
+ }
1464
+ // DB & Auth namespaces
1465
+ enableLogging() {
1466
+ this.engine?.initLogger();
1467
+ this.loggingEnabled = true;
1468
+ if (!process.env.RUST_LOG) {
1469
+ process.env.RUST_LOG = "info";
1470
+ }
1471
+ }
1472
+ getMetrics() {
1473
+ if (!this.engine) throw new Error("Server not started");
1474
+ return this.engine.getMetrics();
1475
+ }
1476
+ onError(handler) {
1477
+ this.errorHandler = handler;
1478
+ return this;
1479
+ }
1480
+ gracefulShutdown(signals = ["SIGINT", "SIGTERM"]) {
1481
+ if (this.shutdownHooksRegistered) return this;
1482
+ this.shutdownHooksRegistered = true;
1483
+ for (const signal of signals) {
1484
+ process.on(signal, () => {
1485
+ this.stop();
1486
+ });
1487
+ }
1488
+ return this;
1489
+ }
1490
+ security() {
1491
+ if (this.engine) {
1492
+ this.engine.setSecurityHeaders(true);
1493
+ } else {
1494
+ this.pendingSecurity = true;
1495
+ }
1496
+ return this;
1497
+ }
1498
+ pendingSecurity = false;
1499
+ registerErrorHooks() {
1500
+ if (this.errorHooksRegistered) return;
1501
+ this.errorHooksRegistered = true;
1502
+ process.on("unhandledRejection", (reason) => {
1503
+ this.handleError(reason);
1504
+ });
1505
+ process.on("uncaughtException", (err) => {
1506
+ this.handleError(err);
1507
+ });
1508
+ }
1509
+ handleError(err, ctx) {
1510
+ if (this.errorHandler) {
1511
+ try {
1512
+ this.errorHandler(err, ctx);
1513
+ return;
1514
+ } catch (handlerError) {
1515
+ console.error("Error handler failed:", handlerError);
1516
+ }
1517
+ }
1518
+ console.error("Unhandled error:", err);
1519
+ }
1520
+ db;
1521
+ auth;
1522
+ static(prefix, root) {
1523
+ if (!prefix.startsWith("/")) prefix = "/" + prefix;
1524
+ const absRoot = __require("path").resolve(root);
1525
+ this.staticRoutes.set(prefix, absRoot);
1526
+ return this;
1527
+ }
1528
+ use(fn) {
1529
+ this.middlewares.push(fn);
1530
+ return this;
1531
+ }
1532
+ ws(path, handler) {
1533
+ if (!path.startsWith("/")) path = "/" + path;
1534
+ this.wsHandlers.set(path, handler);
1535
+ }
1536
+ cors(config = {}) {
1537
+ this.corsConfig = {
1538
+ origin: config.origin || "*",
1539
+ methods: (config.methods || ["GET", "POST", "PUT", "DELETE", "OPTIONS"]).join(", "),
1540
+ headers: (config.headers || ["Content-Type", "Authorization"]).join(", "),
1541
+ credentials: config.credentials || false
1542
+ };
1543
+ return this;
1544
+ }
1545
+ get(path, arg2, arg3) {
1546
+ const fluentBuilder = new FluentBuilder(this, "GET", path);
1547
+ if (arg3) {
1548
+ const route = this.addRoute("GET", path);
1549
+ route.options = arg2;
1550
+ route.handler = arg3;
1551
+ } else if (arg2) {
1552
+ const route = this.addRoute("GET", path);
1553
+ if (typeof arg2 === "function") {
1554
+ route.handler = arg2;
1555
+ } else if ("json" in arg2 || "text" in arg2 || "upload" in arg2) {
1556
+ route.routeConfig = arg2;
1557
+ } else {
1558
+ route.options = arg2;
1559
+ }
1560
+ }
1561
+ return fluentBuilder;
1562
+ }
1563
+ post(path, arg2, arg3) {
1564
+ const fluentBuilder = new FluentBuilder(this, "POST", path);
1565
+ if (arg3) {
1566
+ const route = this.addRoute("POST", path);
1567
+ route.options = arg2;
1568
+ route.handler = arg3;
1569
+ } else if (arg2) {
1570
+ const route = this.addRoute("POST", path);
1571
+ if (typeof arg2 === "function") {
1572
+ route.handler = arg2;
1573
+ } else if ("json" in arg2 || "text" in arg2 || "upload" in arg2) {
1574
+ route.routeConfig = arg2;
1575
+ } else {
1576
+ route.options = arg2;
1577
+ }
1578
+ }
1579
+ return fluentBuilder;
1580
+ }
1581
+ put(path, arg2, arg3) {
1582
+ const fluentBuilder = new FluentBuilder(this, "PUT", path);
1583
+ if (arg3) {
1584
+ const route = this.addRoute("PUT", path);
1585
+ route.options = arg2;
1586
+ route.handler = arg3;
1587
+ } else if (arg2) {
1588
+ const route = this.addRoute("PUT", path);
1589
+ if (typeof arg2 === "function") {
1590
+ route.handler = arg2;
1591
+ } else if ("json" in arg2 || "text" in arg2 || "upload" in arg2) {
1592
+ route.routeConfig = arg2;
1593
+ } else {
1594
+ route.options = arg2;
1595
+ }
1596
+ }
1597
+ return fluentBuilder;
1598
+ }
1599
+ delete(path, arg2, arg3) {
1600
+ const fluentBuilder = new FluentBuilder(this, "DELETE", path);
1601
+ if (arg3) {
1602
+ const route = this.addRoute("DELETE", path);
1603
+ route.options = arg2;
1604
+ route.handler = arg3;
1605
+ } else if (arg2) {
1606
+ const route = this.addRoute("DELETE", path);
1607
+ if (typeof arg2 === "function") {
1608
+ route.handler = arg2;
1609
+ } else if ("json" in arg2 || "text" in arg2 || "upload" in arg2) {
1610
+ route.routeConfig = arg2;
1611
+ } else {
1612
+ route.options = arg2;
1613
+ }
1614
+ }
1615
+ return fluentBuilder;
1616
+ }
1617
+ addRoute(method, path) {
1618
+ const route = new Route(path, method, this);
1619
+ this.routes.push(route);
1620
+ return route;
1621
+ }
1622
+ registerHandler(handler, serializer) {
1623
+ const id = ++this.handlerCounter;
1624
+ this.handlers.set(id, { handler, serializer });
1625
+ return id;
1626
+ }
1627
+ listen(portOrOptions, cb) {
1628
+ let port;
1629
+ let callback = cb;
1630
+ let tls;
1631
+ if (typeof portOrOptions === "object") {
1632
+ port = portOrOptions.port;
1633
+ callback = portOrOptions.callback || cb;
1634
+ tls = portOrOptions.tls;
1635
+ } else {
1636
+ port = portOrOptions;
1637
+ }
1638
+ if (!this.engine) {
1639
+ this.engine = new import_core.NativeEngine(port);
1640
+ this.registerErrorHooks();
1641
+ if (this.loggingEnabled || process.env.RUST_LOG) {
1642
+ if (!process.env.RUST_LOG) process.env.RUST_LOG = "info";
1643
+ this.engine.initLogger();
1644
+ }
1645
+ if (tls) {
1646
+ this.engine.setTls(tls.cert, tls.key);
1647
+ }
1648
+ for (const [prefix, dir] of this.staticRoutes) {
1649
+ this.engine.addStaticRoute(prefix, dir);
1650
+ }
1651
+ if (this.corsConfig) {
1652
+ this.engine.setCors(
1653
+ this.corsConfig.origin,
1654
+ this.corsConfig.methods,
1655
+ this.corsConfig.headers,
1656
+ this.corsConfig.credentials
1657
+ );
1658
+ }
1659
+ if (this.pendingSecurity) {
1660
+ this.engine.setSecurityHeaders(true);
1661
+ }
1662
+ }
1663
+ this.engine.setHandler((event) => {
1664
+ const { handlerId, reqId, params, query, body, headers, url, responseHandle, method } = event;
1665
+ const routeConfig = this.handlers.get(handlerId);
1666
+ if (routeConfig) {
1667
+ const { handler, serializer } = routeConfig;
1668
+ const ctx = new Context(this.engine, reqId, params, query, body, headers, url, responseHandle, method, serializer);
1669
+ const routeMiddleware = async (c, next) => {
1670
+ const res = await handler(c);
1671
+ if (res !== void 0) {
1672
+ c.send(res);
1673
+ }
1674
+ await next();
1675
+ };
1676
+ const fn = compose([...this.middlewares, routeMiddleware]);
1677
+ fn(ctx).catch((err) => {
1678
+ this.handleError(err, ctx);
1679
+ try {
1680
+ ctx.status(500).send({ error: "Internal Server Error" });
1681
+ } catch {
1682
+ }
1683
+ });
1684
+ } else {
1685
+ console.warn(`No handler found for ID ${handlerId}`);
1686
+ }
1687
+ });
1688
+ this.engine.setWsHandler((event) => {
1689
+ const { socketId, eventType, payload, path } = event;
1690
+ if (eventType === "open") {
1691
+ const handler = this.wsHandlers.get(path);
1692
+ if (handler) {
1693
+ this.activeSockets.set(socketId, { handler, path });
1694
+ if (handler.open) {
1695
+ const ws = {
1696
+ send: (msg) => this.engine.wsSend(socketId, msg),
1697
+ subscribe: (room) => this.engine.wsSubscribe(socketId, room),
1698
+ unsubscribe: (room) => this.engine.wsUnsubscribe(socketId, room),
1699
+ publish: (room, msg) => this.engine.wsPublish(room, msg)
1700
+ };
1701
+ handler.open(ws);
1702
+ }
1703
+ }
1704
+ } else {
1705
+ const ctx = this.activeSockets.get(socketId);
1706
+ if (ctx) {
1707
+ const { handler } = ctx;
1708
+ const ws = {
1709
+ send: (msg) => this.engine.wsSend(socketId, msg),
1710
+ subscribe: (room) => this.engine.wsSubscribe(socketId, room),
1711
+ unsubscribe: (room) => this.engine.wsUnsubscribe(socketId, room),
1712
+ publish: (room, msg) => this.engine.wsPublish(room, msg)
1713
+ };
1714
+ if (eventType === "message") {
1715
+ if (handler.message && payload) {
1716
+ handler.message(ws, payload);
1717
+ }
1718
+ } else if (eventType === "close") {
1719
+ if (handler.close) {
1720
+ handler.close(ws);
1721
+ }
1722
+ this.activeSockets.delete(socketId);
1723
+ }
1724
+ }
1725
+ }
1726
+ });
1727
+ for (const route of this.routes) {
1728
+ const enginePath = normalizeRoutePath(route.path);
1729
+ const options = route.options || {};
1730
+ if (options.schema && typeof options.schema !== "string") {
1731
+ options.schema = JSON.stringify(options.schema);
1732
+ }
1733
+ if (route.routeConfig) {
1734
+ if (route.routeConfig.text) {
1735
+ this.engine.registerStaticRoute(route.method, enginePath, route.routeConfig.text, "text/plain", options);
1736
+ } else if (route.routeConfig.json) {
1737
+ const content = typeof route.routeConfig.json === "string" ? route.routeConfig.json : JSON.stringify(route.routeConfig.json);
1738
+ this.engine.registerJsonRoute(route.method, enginePath, content, options);
1739
+ } else if (route.routeConfig.upload) {
1740
+ let handlerId;
1741
+ if (route.routeConfig.upload.handler) {
1742
+ handlerId = this.registerHandler(route.routeConfig.upload.handler);
1743
+ }
1744
+ this.engine.registerUploadRoute(
1745
+ route.method,
1746
+ enginePath,
1747
+ route.routeConfig.upload.dir,
1748
+ handlerId,
1749
+ options
1750
+ );
1751
+ }
1752
+ } else if (route.handlerId) {
1753
+ try {
1754
+ this.engine.registerRoute(route.method, enginePath, route.handlerId, options);
1755
+ } catch (e) {
1756
+ console.error(`Failed to register route ${route.method} ${route.path}:`, e);
1757
+ }
1758
+ }
1759
+ }
1760
+ this.engine.start().then(() => {
1761
+ if (cb) cb();
1762
+ }).catch((err) => {
1763
+ console.error("Native Engine crashed:", err);
1764
+ process.exit(1);
1765
+ });
1766
+ }
1767
+ stop() {
1768
+ if (this.engine) {
1769
+ try {
1770
+ this.engine.stop();
1771
+ } catch (e) {
1772
+ console.error("Failed to stop engine:", e);
1773
+ }
1774
+ }
1775
+ }
1776
+ };
1777
+ var Route = class {
1778
+ constructor(path, method, app) {
1779
+ this.path = path;
1780
+ this.method = method;
1781
+ this.app = app;
1782
+ }
1783
+ handlerId = null;
1784
+ routeConfig;
1785
+ options = {};
1786
+ description;
1787
+ middlewares = [];
1788
+ handler;
1789
+ serializer;
1790
+ desc(description) {
1791
+ this.description = description;
1792
+ return this;
1793
+ }
1794
+ auth(strategy) {
1795
+ return this;
1796
+ }
1797
+ jwt() {
1798
+ this.options.jwt_auth = true;
1799
+ return this;
1800
+ }
1801
+ cache(options) {
1802
+ this.options.cache_ttl = options.ttl;
1803
+ return this;
1804
+ }
1805
+ rateLimit(options) {
1806
+ this.options.rate_limit_limit = options.limit;
1807
+ this.options.rate_limit_window = options.window;
1808
+ return this;
1809
+ }
1810
+ query(schemaBuilder) {
1811
+ const builder = new QuerySchemaBuilder();
1812
+ schemaBuilder(builder);
1813
+ this.options.query_schema = builder.schema;
1814
+ return this;
1815
+ }
1816
+ schema(def) {
1817
+ this.options.schema = def;
1818
+ return this;
1819
+ }
1820
+ priority(level) {
1821
+ this.options.priority = level;
1822
+ return this;
1823
+ }
1824
+ slo(targetMs) {
1825
+ this.options.slo_target = targetMs;
1826
+ return this;
1827
+ }
1828
+ responseSchema(def) {
1829
+ try {
1830
+ this.serializer = fastJson(def);
1831
+ this.options.response_schema = def;
1832
+ } catch (e) {
1833
+ console.error(`Failed to compile response schema for ${this.method} ${this.path}:`, e);
1834
+ }
1835
+ return this;
1836
+ }
1837
+ respond(handler) {
1838
+ if (typeof handler === "object" && handler !== null && ("text" in handler || "json" in handler || "upload" in handler)) {
1839
+ this.routeConfig = handler;
1840
+ } else {
1841
+ const original = handler;
1842
+ const wrapped = async (ctx) => {
1843
+ if (this.options.query_schema) {
1844
+ const result = normalizeQuery(this.options.query_schema, ctx);
1845
+ if (!result.ok) {
1846
+ const errors = "errors" in result && result.errors ? result.errors : [];
1847
+ ctx.status(400).send({ error: errors.join(", ") });
1848
+ return;
1849
+ }
1850
+ ctx._query = result.query;
1851
+ }
1852
+ return original(ctx);
1853
+ };
1854
+ this.handlerId = this.app.registerHandler(wrapped, this.serializer);
1855
+ }
1856
+ }
1857
+ };
1858
+ export {
1859
+ FluentBuilder,
1860
+ Q,
1861
+ TestClient,
1862
+ createTestClient
1863
+ };