@coderbuzz/ken 0.1.0

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.
@@ -0,0 +1,622 @@
1
+ import {
2
+ canUseNativeRoute
3
+ } from "./chunk-2BOPD5H7.js";
4
+ import {
5
+ BaseContext
6
+ } from "./chunk-2MK26YDD.js";
7
+ import {
8
+ EMPTY_PARAMS,
9
+ Router,
10
+ WS_DEFAULTS,
11
+ WsReadyState,
12
+ createExecutor,
13
+ createNotFoundExecutor,
14
+ toResponse
15
+ } from "./chunk-DPU3PBLP.js";
16
+
17
+ // src/context/uws.ts
18
+ var UwsContext = class extends BaseContext {
19
+ res;
20
+ // Pre-read values (must be read sync during callback)
21
+ _method;
22
+ _url;
23
+ _rawQuery;
24
+ _headersMap;
25
+ _bodyBuffer = null;
26
+ /**
27
+ * Constructor - MUST read all request data synchronously.
28
+ * uWs HttpRequest becomes invalid after callback returns.
29
+ */
30
+ constructor(req, res, params = EMPTY_PARAMS, getRemoteInfo, schema) {
31
+ super(params, getRemoteInfo, schema);
32
+ this.res = res;
33
+ this._method = req.getMethod().toUpperCase();
34
+ this._url = req.getUrl();
35
+ this._rawQuery = req.getQuery();
36
+ this._headersMap = /* @__PURE__ */ new Map();
37
+ req.forEach((key, value) => {
38
+ this._headersMap.set(key.toLowerCase(), value);
39
+ });
40
+ }
41
+ get url() {
42
+ return this._rawQuery ? `${this._url}?${this._rawQuery}` : this._url;
43
+ }
44
+ get method() {
45
+ return this._method;
46
+ }
47
+ get body() {
48
+ return null;
49
+ }
50
+ _getRawHeaders() {
51
+ return {
52
+ get: (key) => this._headersMap.get(key.toLowerCase()) ?? null
53
+ };
54
+ }
55
+ _getRawUrl() {
56
+ return this.url;
57
+ }
58
+ /**
59
+ * Lazily parses and validates headers.
60
+ */
61
+ get headers() {
62
+ let cached = this._headers;
63
+ if (cached !== null) return cached;
64
+ const schema = this._schema?.headers;
65
+ if (schema !== void 0) {
66
+ const result = {};
67
+ for (const key in schema) {
68
+ const validator = schema[key];
69
+ result[key] = validator(this._headersMap.get(key.toLowerCase()) || "");
70
+ }
71
+ this._headers = result;
72
+ return result;
73
+ }
74
+ const h = {};
75
+ this._headersMap.forEach((v, k) => h[k] = v);
76
+ this._headers = h;
77
+ return h;
78
+ }
79
+ /**
80
+ * Read body buffer from uWs response
81
+ */
82
+ _readBody() {
83
+ if (this._bodyBuffer !== null) return Promise.resolve(this._bodyBuffer);
84
+ return new Promise((resolve, reject) => {
85
+ const chunks = [];
86
+ this.res.onData((chunk, isLast) => {
87
+ chunks.push(Buffer.from(chunk));
88
+ if (isLast) {
89
+ this._bodyBuffer = Buffer.concat(chunks);
90
+ resolve(this._bodyBuffer);
91
+ }
92
+ });
93
+ this.res.onAborted(() => {
94
+ reject(new Error("Request aborted"));
95
+ });
96
+ });
97
+ }
98
+ /**
99
+ * Lazily parses and validates JSON body.
100
+ */
101
+ get json() {
102
+ if (this._json) return this._json;
103
+ const validator = this._schema?.json;
104
+ if (validator) {
105
+ this._json = (async () => {
106
+ const buffer = await this._readBody();
107
+ const data = JSON.parse(buffer.toString("utf8"));
108
+ try {
109
+ return validator(data);
110
+ } catch (err) {
111
+ throw new Error(`JSON Body validation failed: ${err.message}`);
112
+ }
113
+ })();
114
+ } else {
115
+ this._json = (async () => {
116
+ try {
117
+ const buffer = await this._readBody();
118
+ return JSON.parse(buffer.toString("utf8"));
119
+ } catch {
120
+ return null;
121
+ }
122
+ })();
123
+ }
124
+ return this._json;
125
+ }
126
+ /**
127
+ * Lazily parses and validates text body.
128
+ */
129
+ get text() {
130
+ if (this._text) return this._text;
131
+ const validator = this._schema?.text;
132
+ if (validator) {
133
+ this._text = (async () => {
134
+ const buffer = await this._readBody();
135
+ const data = buffer.toString("utf8");
136
+ try {
137
+ return validator(data);
138
+ } catch (err) {
139
+ throw new Error(`Text Body validation failed: ${err.message}`);
140
+ }
141
+ })();
142
+ } else {
143
+ this._text = (async () => {
144
+ const buffer = await this._readBody();
145
+ return buffer.toString("utf8");
146
+ })();
147
+ }
148
+ return this._text;
149
+ }
150
+ /**
151
+ * Lazily parses and validates form body.
152
+ * Supports both application/x-www-form-urlencoded and multipart/form-data
153
+ */
154
+ get form() {
155
+ if (this._form) return this._form;
156
+ const schema = this._schema?.form;
157
+ this._form = (async () => {
158
+ const buffer = await this._readBody();
159
+ const contentType = this._headersMap.get("content-type") || "";
160
+ if (contentType.startsWith("multipart/form-data")) {
161
+ const fullUrl = `http://localhost${this.url}`;
162
+ const headers = new Headers();
163
+ this._headersMap.forEach((value, key) => {
164
+ headers.set(key, value);
165
+ });
166
+ const request = new Request(fullUrl, {
167
+ method: this.method,
168
+ headers,
169
+ body: new Uint8Array(buffer),
170
+ // @ts-ignore - duplex is needed for streaming
171
+ duplex: "half"
172
+ });
173
+ const formData2 = await request.formData();
174
+ if (schema) {
175
+ const result = {};
176
+ for (const key in schema) {
177
+ try {
178
+ result[key] = schema[key](formData2.get(key));
179
+ } catch (err) {
180
+ throw new Error(`Form Body validation failed for "${key}": ${err.message}`);
181
+ }
182
+ }
183
+ return result;
184
+ }
185
+ return formData2;
186
+ }
187
+ const text = buffer.toString("utf8");
188
+ const formData = /* @__PURE__ */ new Map();
189
+ const pairs = text.split("&");
190
+ for (const pair of pairs) {
191
+ const [key, value] = pair.split("=");
192
+ if (key) {
193
+ formData.set(
194
+ decodeURIComponent(key),
195
+ value ? decodeURIComponent(value.replace(/\+/g, " ")) : ""
196
+ );
197
+ }
198
+ }
199
+ if (schema) {
200
+ const result = {};
201
+ for (const key in schema) {
202
+ try {
203
+ result[key] = schema[key](formData.get(key) ?? null);
204
+ } catch (err) {
205
+ throw new Error(`Form Body validation failed for "${key}": ${err.message}`);
206
+ }
207
+ }
208
+ return result;
209
+ }
210
+ return Object.fromEntries(formData);
211
+ })();
212
+ return this._form;
213
+ }
214
+ };
215
+
216
+ // src/ws/uws.ts
217
+ var textDecoder = new TextDecoder();
218
+ var UwsWsPeer = class {
219
+ ws;
220
+ _remoteAddress = null;
221
+ readyState = WsReadyState.OPEN;
222
+ constructor(ws) {
223
+ this.ws = ws;
224
+ }
225
+ get data() {
226
+ return this.ws.getUserData().userData;
227
+ }
228
+ set data(value) {
229
+ this.ws.getUserData().userData = value;
230
+ }
231
+ get remoteAddress() {
232
+ if (this._remoteAddress === null) {
233
+ this._remoteAddress = textDecoder.decode(this.ws.getRemoteAddressAsText());
234
+ }
235
+ return this._remoteAddress;
236
+ }
237
+ send(data, compress) {
238
+ const isBinary = typeof data !== "string";
239
+ return this.ws.send(data, isBinary, compress);
240
+ }
241
+ close(code, reason) {
242
+ this.ws.end(code, reason);
243
+ }
244
+ subscribe(topic) {
245
+ this.ws.subscribe(topic);
246
+ }
247
+ unsubscribe(topic) {
248
+ this.ws.unsubscribe(topic);
249
+ }
250
+ publish(topic, data, compress) {
251
+ const isBinary = typeof data !== "string";
252
+ this.ws.publish(topic, data, isBinary, compress);
253
+ }
254
+ isSubscribed(topic) {
255
+ return this.ws.isSubscribed(topic);
256
+ }
257
+ ping(data) {
258
+ this.ws.ping(data);
259
+ }
260
+ pong(_data) {
261
+ }
262
+ };
263
+ function createUwsWsConfig(handler, options = {}) {
264
+ const opts = { ...WS_DEFAULTS, ...options };
265
+ const peerMap = /* @__PURE__ */ new WeakMap();
266
+ function getPeer(ws) {
267
+ let peer = peerMap.get(ws);
268
+ if (!peer) {
269
+ peer = new UwsWsPeer(ws);
270
+ peerMap.set(ws, peer);
271
+ }
272
+ return peer;
273
+ }
274
+ const behavior = {
275
+ maxPayloadLength: opts.maxPayloadLength,
276
+ maxBackpressure: opts.backpressureLimit,
277
+ idleTimeout: opts.idleTimeout,
278
+ sendPingsAutomatically: opts.pingInterval > 0,
279
+ compression: opts.perMessageDeflate ? 1 : 0,
280
+ // SHARED_COMPRESSOR
281
+ upgrade: handler.upgrade ? async (res, req, context) => {
282
+ const headers = new Headers();
283
+ req.forEach((key, value) => {
284
+ headers.set(key, value);
285
+ });
286
+ const query = req.getQuery();
287
+ const url = `http://localhost${req.getUrl()}${query ? "?" + query : ""}`;
288
+ const request = new Request(url, { method: req.getMethod().toUpperCase(), headers });
289
+ let aborted = false;
290
+ res.onAborted(() => {
291
+ aborted = true;
292
+ });
293
+ try {
294
+ const result = await handler.upgrade(request);
295
+ if (aborted) return;
296
+ if (result instanceof Response) {
297
+ res.cork(() => {
298
+ res.writeStatus(`${result.status}`);
299
+ result.headers.forEach((v, k) => {
300
+ res.writeHeader(k, v);
301
+ });
302
+ res.end();
303
+ });
304
+ return;
305
+ }
306
+ res.cork(() => {
307
+ res.upgrade(
308
+ { userData: result },
309
+ req.getHeader("sec-websocket-key"),
310
+ req.getHeader("sec-websocket-protocol"),
311
+ req.getHeader("sec-websocket-extensions"),
312
+ context
313
+ );
314
+ });
315
+ } catch {
316
+ if (!aborted) {
317
+ res.cork(() => {
318
+ res.writeStatus("500").end();
319
+ });
320
+ }
321
+ }
322
+ } : void 0,
323
+ open(ws) {
324
+ const peer = getPeer(ws);
325
+ try {
326
+ handler.open?.(peer);
327
+ } catch {
328
+ }
329
+ },
330
+ message(ws, message, isBinary) {
331
+ const peer = getPeer(ws);
332
+ try {
333
+ const data = isBinary ? message : new TextDecoder().decode(message);
334
+ handler.message(peer, data);
335
+ } catch {
336
+ }
337
+ },
338
+ close(ws, code, message) {
339
+ const peer = getPeer(ws);
340
+ peer.readyState = WsReadyState.CLOSED;
341
+ peerMap.delete(ws);
342
+ const reason = message.byteLength > 0 ? new TextDecoder().decode(message) : "";
343
+ try {
344
+ handler.close?.(peer, code, reason);
345
+ } catch {
346
+ }
347
+ },
348
+ ping: handler.ping ? (ws, message) => {
349
+ const peer = getPeer(ws);
350
+ try {
351
+ handler.ping(peer, message);
352
+ } catch {
353
+ }
354
+ } : void 0,
355
+ pong: handler.pong ? (ws, message) => {
356
+ const peer = getPeer(ws);
357
+ try {
358
+ handler.pong(peer, message);
359
+ } catch {
360
+ }
361
+ } : void 0
362
+ };
363
+ return { behavior };
364
+ }
365
+
366
+ // src/runtime/uws.ts
367
+ var textDecoder2 = new TextDecoder();
368
+ async function sendUwsResponse(uwsRes, response, aborted) {
369
+ if (aborted.value) return;
370
+ const status = `${response.status} ${response.statusText || "OK"}`;
371
+ const body = response.body;
372
+ uwsRes.cork(() => {
373
+ uwsRes.writeStatus(status);
374
+ response.headers.forEach((value, key) => {
375
+ uwsRes.writeHeader(key, value);
376
+ });
377
+ if (!body) {
378
+ uwsRes.end();
379
+ }
380
+ });
381
+ if (body && !aborted.value) {
382
+ const reader = body.getReader();
383
+ try {
384
+ while (!aborted.value) {
385
+ const { done, value } = await reader.read();
386
+ if (done) break;
387
+ uwsRes.cork(() => {
388
+ uwsRes.write(value);
389
+ });
390
+ }
391
+ } finally {
392
+ reader.releaseLock();
393
+ if (!aborted.value) {
394
+ uwsRes.cork(() => {
395
+ uwsRes.end();
396
+ });
397
+ }
398
+ }
399
+ }
400
+ }
401
+ function getRemoteInfoFromUws(res) {
402
+ return () => {
403
+ const addressBuffer = res.getRemoteAddressAsText();
404
+ const address = textDecoder2.decode(addressBuffer);
405
+ return {
406
+ address,
407
+ port: 0
408
+ // uWS doesn't provide port
409
+ };
410
+ };
411
+ }
412
+ function toUwsPattern(kenPath) {
413
+ return kenPath;
414
+ }
415
+ function extractUwsParams(req, pattern) {
416
+ const paramNames = [];
417
+ const parts = pattern.split("/");
418
+ for (const part of parts) {
419
+ if (part.startsWith(":")) {
420
+ paramNames.push(part.slice(1).replace("?", ""));
421
+ }
422
+ }
423
+ if (paramNames.length === 0) {
424
+ return {};
425
+ }
426
+ const params = {};
427
+ for (let i = 0; i < paramNames.length; i++) {
428
+ const value = req.getParameter(i);
429
+ if (value) {
430
+ params[paramNames[i]] = value;
431
+ }
432
+ }
433
+ return params;
434
+ }
435
+ var uwsContextFactory = ({ req, res }, params, getRemoteInfo, schema) => new UwsContext(req, res, params, getRemoteInfo, schema);
436
+ function server({ port, hostname, router }) {
437
+ let listenSocket = void 0;
438
+ const getUws = async () => {
439
+ try {
440
+ const uws = await import("uWebSockets.js");
441
+ return uws;
442
+ } catch {
443
+ throw new Error("uWebSockets.js is required for uWS runtime. Install with: npm install uWebSockets.js");
444
+ }
445
+ };
446
+ return {
447
+ async run() {
448
+ if (listenSocket) {
449
+ listenSocket.close();
450
+ }
451
+ const uws = await getUws();
452
+ const app = uws.App();
453
+ const wsRoutes = router.wsRoutes || [];
454
+ for (const wsRoute of wsRoutes) {
455
+ const wsConfig = createUwsWsConfig(wsRoute.handler, wsRoute.options);
456
+ app.ws(wsRoute.path, wsConfig.behavior);
457
+ }
458
+ const nativeRoutes = [];
459
+ const fallbackRoutes = [];
460
+ for (const route of router.routes) {
461
+ let mergedSchema = route.schema;
462
+ if (typeof router.matchMiddleware === "function" && typeof router.mergeSchemas === "function") {
463
+ const matchedMiddleware = router.matchMiddleware(route.path);
464
+ mergedSchema = router.mergeSchemas(matchedMiddleware, route.schema);
465
+ }
466
+ if (canUseNativeRoute(route.path)) {
467
+ nativeRoutes.push({
468
+ method: route.method,
469
+ pattern: route.path,
470
+ handler: route.handler,
471
+ schema: mergedSchema,
472
+ staticValue: route.staticValue
473
+ });
474
+ } else {
475
+ fallbackRoutes.push({ ...route, schema: mergedSchema });
476
+ }
477
+ }
478
+ for (const route of nativeRoutes) {
479
+ const uwsPattern = toUwsPattern(route.pattern);
480
+ const methodLower = route.method.toLowerCase();
481
+ const registerMethod = methodLower === "delete" ? "del" : methodLower;
482
+ if (route.staticValue !== void 0) {
483
+ const cachedResponse = toResponse(route.staticValue);
484
+ const status = `${cachedResponse.status} ${cachedResponse.statusText || "OK"}`;
485
+ const headers = [];
486
+ cachedResponse.headers.forEach((v, k) => headers.push([k, v]));
487
+ cachedResponse.text().then((body) => {
488
+ const headerCount = headers.length;
489
+ if (headerCount === 1) {
490
+ const [hk, hv] = headers[0];
491
+ app[registerMethod](uwsPattern, (res) => {
492
+ res.cork(() => {
493
+ res.writeStatus(status);
494
+ res.writeHeader(hk, hv);
495
+ res.end(body);
496
+ });
497
+ });
498
+ } else {
499
+ app[registerMethod](uwsPattern, (res) => {
500
+ res.cork(() => {
501
+ res.writeStatus(status);
502
+ for (let i = 0; i < headerCount; i++) {
503
+ res.writeHeader(headers[i][0], headers[i][1]);
504
+ }
505
+ res.end(body);
506
+ });
507
+ });
508
+ }
509
+ });
510
+ } else if (route.handler) {
511
+ const executor = createExecutor(uwsContextFactory, route.handler, route.schema);
512
+ const pattern = route.pattern;
513
+ app[registerMethod](uwsPattern, async (res, req) => {
514
+ const aborted = { value: false };
515
+ res.onAborted(() => {
516
+ aborted.value = true;
517
+ });
518
+ const params = extractUwsParams(req, pattern);
519
+ const getRemoteInfo = getRemoteInfoFromUws(res);
520
+ try {
521
+ const result = await executor({ req, res }, params, getRemoteInfo);
522
+ if (!aborted.value) {
523
+ const response = toResponse(result);
524
+ await sendUwsResponse(res, response, aborted);
525
+ }
526
+ } catch (error) {
527
+ if (!aborted.value) {
528
+ const errResponse = error instanceof Response ? error : new Response(error instanceof Error ? error.message : String(error), { status: 500 });
529
+ await sendUwsResponse(res, errResponse, aborted);
530
+ }
531
+ }
532
+ });
533
+ }
534
+ }
535
+ const fallbackRouter = new Router();
536
+ let match = null;
537
+ if (fallbackRoutes.length > 0) {
538
+ for (const route of fallbackRoutes) {
539
+ if (route.handler) {
540
+ const executor = createExecutor(uwsContextFactory, route.handler, route.schema);
541
+ fallbackRouter.registerCompiled(route.method, route.path, executor, route.schema);
542
+ } else if (route.staticValue !== void 0) {
543
+ const staticValue = route.staticValue;
544
+ fallbackRouter.registerCompiled(route.method, route.path, () => staticValue, route.schema, toResponse(staticValue));
545
+ }
546
+ }
547
+ match = fallbackRouter.matcher();
548
+ }
549
+ const notFoundExecutor = createNotFoundExecutor(router, uwsContextFactory);
550
+ app.any("/*", async (res, req) => {
551
+ const aborted = { value: false };
552
+ res.onAborted(() => {
553
+ aborted.value = true;
554
+ });
555
+ const method = req.getMethod().toUpperCase();
556
+ const pathname = req.getUrl();
557
+ const matchResult = match ? match(method, pathname) : void 0;
558
+ if (matchResult !== void 0) {
559
+ const getRemoteInfo = getRemoteInfoFromUws(res);
560
+ try {
561
+ if (matchResult.response !== void 0) {
562
+ await sendUwsResponse(res, matchResult.response.clone(), aborted);
563
+ } else {
564
+ const result = await matchResult.handler({ req, res }, matchResult.params, getRemoteInfo);
565
+ if (!aborted.value) {
566
+ const response = toResponse(result);
567
+ await sendUwsResponse(res, response, aborted);
568
+ }
569
+ }
570
+ } catch (error) {
571
+ if (!aborted.value) {
572
+ const errResponse = error instanceof Response ? error : new Response(error instanceof Error ? error.message : String(error), { status: 500 });
573
+ await sendUwsResponse(res, errResponse, aborted);
574
+ }
575
+ }
576
+ } else {
577
+ if (notFoundExecutor) {
578
+ const getRemoteInfo = getRemoteInfoFromUws(res);
579
+ try {
580
+ const result = await notFoundExecutor({ req, res }, getRemoteInfo, pathname);
581
+ if (!aborted.value) {
582
+ const response = toResponse(result);
583
+ await sendUwsResponse(res, response, aborted);
584
+ }
585
+ } catch (error) {
586
+ if (!aborted.value) {
587
+ const errResponse = error instanceof Response ? error : new Response(error instanceof Error ? error.message : String(error), { status: 500 });
588
+ await sendUwsResponse(res, errResponse, aborted);
589
+ }
590
+ }
591
+ } else {
592
+ res.cork(() => {
593
+ res.writeStatus("404").end("Not Found");
594
+ });
595
+ }
596
+ }
597
+ });
598
+ return new Promise((resolve, reject) => {
599
+ const listenPort = port || 3e3;
600
+ const listenHost = hostname || "0.0.0.0";
601
+ app.listen(listenHost, listenPort, (token) => {
602
+ if (token) {
603
+ listenSocket = token;
604
+ resolve({ hostname: listenHost, port: listenPort });
605
+ } else {
606
+ reject(new Error(`Failed to listen on ${listenHost}:${listenPort}`));
607
+ }
608
+ });
609
+ });
610
+ },
611
+ stop() {
612
+ if (listenSocket) {
613
+ listenSocket.close();
614
+ listenSocket = void 0;
615
+ }
616
+ }
617
+ };
618
+ }
619
+ export {
620
+ server
621
+ };
622
+ //# sourceMappingURL=uws-VNY2LPIZ.js.map