@wooksjs/event-http 0.6.1 → 0.6.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.
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ const wooks = __toESM(require("wooks"));
32
32
  const stream = __toESM(require("stream"));
33
33
 
34
34
  //#region packages/event-http/src/event-http.ts
35
+ /** Creates an async event context for an incoming HTTP request/response pair. */
35
36
  function createHttpContext(data, options) {
36
37
  return (0, __wooksjs_event_core.createAsyncEventContext)({
37
38
  event: {
@@ -57,7 +58,7 @@ function escapeRegex(s) {
57
58
  function safeDecode(f, v) {
58
59
  try {
59
60
  return f(v);
60
- } catch (error) {
61
+ } catch {
61
62
  return v;
62
63
  }
63
64
  }
@@ -89,7 +90,12 @@ const units = {
89
90
 
90
91
  //#endregion
91
92
  //#region packages/event-http/src/utils/set-cookie.ts
93
+ const COOKIE_NAME_RE = /^[\w!#$%&'*+\-.^`|~]+$/;
94
+ function sanitizeCookieAttrValue(v) {
95
+ return v.replace(/[;\r\n]/g, "");
96
+ }
92
97
  function renderCookie(key, data) {
98
+ if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
93
99
  let attrs = "";
94
100
  for (const [a, v] of Object.entries(data.attrs)) {
95
101
  const func = cookieAttrFunc[a];
@@ -103,8 +109,8 @@ function renderCookie(key, data) {
103
109
  const cookieAttrFunc = {
104
110
  expires: (v) => `Expires=${typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString()}`,
105
111
  maxAge: (v) => `Max-Age=${convertTime(v, "s").toString()}`,
106
- domain: (v) => `Domain=${v}`,
107
- path: (v) => `Path=${v}`,
112
+ domain: (v) => `Domain=${sanitizeCookieAttrValue(String(v))}`,
113
+ path: (v) => `Path=${sanitizeCookieAttrValue(String(v))}`,
108
114
  secure: (v) => v ? "Secure" : "",
109
115
  httpOnly: (v) => v ? "HttpOnly" : "",
110
116
  sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
@@ -125,7 +131,7 @@ function encodingSupportsStream(encodings) {
125
131
  }
126
132
  async function uncompressBody(encodings, compressed) {
127
133
  let buf = compressed;
128
- for (const enc of encodings.slice().reverse()) {
134
+ for (const enc of encodings.slice().toReversed()) {
129
135
  const c = compressors[enc];
130
136
  if (!c) throw new Error(`Unsupported compression type "${enc}".`);
131
137
  buf = await c.uncompress(buf);
@@ -135,7 +141,7 @@ async function uncompressBody(encodings, compressed) {
135
141
  async function uncompressBodyStream(encodings, src) {
136
142
  if (!encodingSupportsStream(encodings)) throw new Error("Some encodings lack a streaming decompressor");
137
143
  let out = src;
138
- for (const enc of Array.from(encodings).reverse()) out = await compressors[enc].stream.uncompress(out);
144
+ for (const enc of Array.from(encodings).toReversed()) out = await compressors[enc].stream.uncompress(out);
139
145
  return out;
140
146
  }
141
147
 
@@ -338,24 +344,395 @@ let EHttpStatusCode = /* @__PURE__ */ function(EHttpStatusCode$1) {
338
344
  return EHttpStatusCode$1;
339
345
  }({});
340
346
 
347
+ //#endregion
348
+ //#region packages/event-http/src/errors/403.tl.svg
349
+ function _403_tl_default(ctx) {
350
+ return `<svg height="64" viewBox="0 4 100 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#888888" stroke-width="2">
351
+ <path d="M50 90.625C64.4042 87.1875 83.5751 69.8667 83.5751 48.6937V24.0854L50 9.375L16.425 24.0833V48.6937C16.425 69.8667 35.5959 87.1875 50 90.625Z" fill="#ff000050">
352
+ <animate attributeName="fill" dur="2s" repeatCount="indefinite"
353
+ values="#ff000000;#ff000050;#ff000000" />
354
+ </path>
355
+
356
+ <path d="M61.5395 46.0812H38.4604C37.1061 46.0812 36.0083 47.1791 36.0083 48.5333V65.075C36.0083 66.4292 37.1061 67.5271 38.4604 67.5271H61.5395C62.8938 67.5271 63.9916 66.4292 63.9916 65.075V48.5333C63.9916 47.1791 62.8938 46.0812 61.5395 46.0812Z" />
357
+
358
+ <path d="M41.7834 46.0834V39.6813C41.7834 37.5021 42.6491 35.4121 44.1901 33.8712C45.731 32.3303 47.8209 31.4646 50.0001 31.4646C52.1793 31.4646 54.2693 32.3303 55.8102 33.8712C57.3511 35.4121 58.2168 37.5021 58.2168 39.6813V46.0813" />
359
+ </svg>
360
+ `;
361
+ }
362
+
363
+ //#endregion
364
+ //#region packages/event-http/src/errors/404.tl.svg
365
+ function _404_tl_default(ctx) {
366
+ return `<svg height="64" viewBox="0 20 100 64" fill="none" xmlns="http://www.w3.org/2000/svg">
367
+ <defs>
368
+ <path id="sheet" d="M86.5 36.5V47.5H97.5V92.5H52.5V36.5H86.5ZM63 68.5H87V67.5H63V68.5ZM63 63.5H87V62.5H63V63.5ZM63 58.5H87V57.5H63V58.5ZM96.793 46.5H87.5V37.207L96.793 46.5Z" fill="#88888833" stroke="#888888aa"/>
369
+ </defs>
370
+
371
+ <g id="queue" transform="translate(-5 -10)">
372
+ <use href="#sheet" opacity="0">
373
+ <animateTransform attributeName="transform" type="translate"
374
+ dur="3s" repeatCount="indefinite"
375
+ keyTimes="0;0.1;0.32;0.42;1"
376
+ values="30 0; -20 0; -20 0; -70 0; -70 0" />
377
+ <animate attributeName="opacity"
378
+ dur="3s" repeatCount="indefinite"
379
+ keyTimes="0;0.1;0.32;0.42;1"
380
+ values="0;1;1;0;0" />
381
+ </use>
382
+
383
+ <use href="#sheet" opacity="0">
384
+ <animateTransform attributeName="transform" type="translate"
385
+ dur="3s" begin="1s" repeatCount="indefinite"
386
+ keyTimes="0;0.1;0.32;0.42;1"
387
+ values="30 0; -20 0; -20 0; -70 0; -70 0" />
388
+ <animate attributeName="opacity"
389
+ dur="3s" begin="1s" repeatCount="indefinite"
390
+ keyTimes="0;0.1;0.32;0.42;1"
391
+ values="0;1;1;0;0" />
392
+ </use>
393
+
394
+ <use href="#sheet" opacity="0">
395
+ <animateTransform attributeName="transform" type="translate"
396
+ dur="3s" begin="2s" repeatCount="indefinite"
397
+ keyTimes="0;0.1;0.32;0.42;1"
398
+ values="30 0; -20 0; -20 0; -70 0; -70 0" />
399
+ <animate attributeName="opacity"
400
+ dur="3s" begin="2s" repeatCount="indefinite"
401
+ keyTimes="0;0.1;0.32;0.42;1"
402
+ values="0;1;1;0;0" />
403
+ </use>
404
+ </g>
405
+
406
+ <g>
407
+ <path d="M49.5 32.5C58.3366 32.5 65.5 39.6634 65.5 48.5C65.5 54.4781 62.222 59.6923 57.3584 62.4404C55.0386 63.7512 52.3591 64.5 49.5 64.5C40.6634 64.5 33.5 57.3366 33.5 48.5C33.5 39.6634 40.6634 32.5 49.5 32.5Z" fill="#ffffff50" stroke="#888888" stroke-width="3"/>
408
+
409
+ <path d="M62.7101 74.5691C63.117 75.2907 64.0318 75.5459 64.7534 75.139C65.4751 74.7321 65.7302 73.8173 65.3233 73.0957L62.7101 74.5691ZM58.05 63.25L56.7434 63.9867L62.7101 74.5691L64.0167 73.8324L65.3233 73.0957L59.3567 62.5133L58.05 63.25Z" fill="#888888"/>
410
+ </g>
411
+ </svg>
412
+
413
+ `;
414
+ }
415
+
416
+ //#endregion
417
+ //#region packages/event-http/src/errors/500.tl.svg
418
+ function _500_tl_default(ctx) {
419
+ return `<svg height="64" viewBox="0 0 120 100" xmlns="http://www.w3.org/2000/svg">
420
+
421
+ <g id="server">
422
+
423
+ <g fill="#88888888" stroke="#88888888" stroke-width="2" >
424
+ <path d="M18 90C13.5817 90 10 86.4182 10 82V38C10 33.5817 13.5817 30 18 30H50.5L58 43L52.5 53L56 68.5L49.0098 89.97L61.2141 71.4358L58.363 54.315L64.7769 43.5511L58.2763 30.2943L104.243 32.0434C108.658 32.2114 112.101 35.9267 111.933 40.3418L110.26 84.31C110.092 88.725 106.377 92.168 101.962 92L49 90H18Z" />
425
+ </g>
426
+ <circle cx="30" cy="60" r="6" fill="red">
427
+ <animate attributeName="fill" dur="0.8s"
428
+ values="red;#2d0000;red" repeatCount="indefinite"/>
429
+ </circle>
430
+
431
+ </g>
432
+
433
+ <g fill="lightgray" opacity="0.75">
434
+ <circle cx="50" cy="35" r="6">
435
+ <animate attributeName="cy" from="35" to="15" dur="2s"
436
+ repeatCount="indefinite"/>
437
+ <animate attributeName="opacity" values="0.75;0" dur="2s"
438
+ repeatCount="indefinite"/>
439
+ </circle>
440
+ <circle cx="60" cy="40" r="4">
441
+ <animate attributeName="cy" from="40" to="20" dur="2s"
442
+ begin="0.4s" repeatCount="indefinite"/>
443
+ <animate attributeName="opacity" values="0.75;0" dur="2s"
444
+ begin="0.4s" repeatCount="indefinite"/>
445
+ </circle>
446
+ </g>
447
+
448
+ </svg>
449
+ `;
450
+ }
451
+
452
+ //#endregion
453
+ //#region packages/event-http/src/errors/error.tl.html
454
+ function error_tl_default(ctx) {
455
+ const { statusCode, statusMessage, icon, message, details, link, image, version } = ctx;
456
+ return `<!doctype html>
457
+ <html lang="en">
458
+ <head>
459
+ <meta charset="UTF-8" />
460
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
461
+ <title>${statusCode} ${statusMessage}</title>
462
+ <style>
463
+ body {
464
+ font-family:
465
+ -apple-system, BlinkMacMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
466
+ 'Open Sans', 'Helvetica Neue', sans-serif;
467
+ display: flex;
468
+ justify-content: center;
469
+ align-items: flex-start;
470
+ min-height: 100vh;
471
+ margin: 0;
472
+ padding: 0 20px;
473
+ box-sizing: border-box;
474
+ transition:
475
+ background-color 0.3s ease,
476
+ color 0.3s ease;
477
+ }
478
+
479
+ .error-container {
480
+ padding: 48px;
481
+ padding-bottom: 12px !important;
482
+ background-color: #ffffff;
483
+ border-radius: 0 0 12px 12px;
484
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
485
+ text-align: center;
486
+ max-width: 650px;
487
+ width: 100%;
488
+ transition:
489
+ background-color 0.3s ease,
490
+ border-color 0.3s ease,
491
+ box-shadow 0.3s ease;
492
+ }
493
+
494
+ .status-code {
495
+ font-size: 5rem;
496
+ font-weight: 900;
497
+ margin-bottom: 5px;
498
+ line-height: 1;
499
+ transition: color 0.3s ease;
500
+ display: flex;
501
+ align-items: center;
502
+ justify-content: center;
503
+ gap: 1rem;
504
+ position: relative;
505
+ margin-right: 24px;
506
+ }
507
+
508
+ .status-text {
509
+ font-size: 2.25rem;
510
+ font-weight: 700;
511
+ margin-bottom: 25px;
512
+ transition: color 0.3s ease;
513
+ }
514
+
515
+ .error-message {
516
+ font-size: 1.25rem;
517
+ margin-bottom: 40px;
518
+ line-height: 1.7;
519
+ transition: color 0.3s ease;
520
+ }
521
+
522
+ .json-details-container {
523
+ padding: 20px;
524
+ border-radius: 8px;
525
+ text-align: left;
526
+ overflow-x: auto;
527
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
528
+ font-size: 0.9rem;
529
+ border: 1px solid;
530
+ transition:
531
+ background-color 0.3s ease,
532
+ color 0.3s ease,
533
+ border-color 0.3s ease;
534
+ }
535
+
536
+ .json-details-container pre {
537
+ margin: 0;
538
+ white-space: pre-wrap;
539
+ word-break: break-all;
540
+ }
541
+
542
+ .json-details-container code {
543
+ display: block;
544
+ }
545
+
546
+ body {
547
+ background-color: #f8fafc;
548
+ color: #1f2937;
549
+ }
550
+ .error-container {
551
+ background-color: #ffffff;
552
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
553
+ }
554
+ .status-code {
555
+ color: #dc2626;
556
+ }
557
+ .status-text {
558
+ color: #1f2937;
559
+ }
560
+ .error-message {
561
+ color: #4b5563;
562
+ }
563
+ .json-details-container {
564
+ background-color: #f0f4f8;
565
+ color: #374151;
566
+ border-color: #d1d5db;
567
+ }
568
+ .json-details-container p {
569
+ color: #6b7280;
570
+ }
571
+
572
+ @media (prefers-color-scheme: dark) {
573
+ body {
574
+ background-color: #2d3748;
575
+ color: #e2e8f0;
576
+ }
577
+ .error-container {
578
+ background-color: #1a202c;
579
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
580
+ }
581
+ .status-code {
582
+ color: #f56565;
583
+ }
584
+ .status-text {
585
+ color: #cbd5e1;
586
+ }
587
+ .error-message {
588
+ color: #a0aec0;
589
+ }
590
+ .json-details-container {
591
+ background-color: #2d3748;
592
+ color: #e2e8f0;
593
+ border-color: #4a5568;
594
+ }
595
+ .json-details-container p {
596
+ color: #a0aec0;
597
+ }
598
+ }
599
+
600
+ .footer {
601
+ display: flex;
602
+ gap: 0.25rem;
603
+ font-size: 0.6em;
604
+ justify-content: flex-end;
605
+ align-items: center;
606
+ margin-top: 12px;
607
+ opacity: 0.5;
608
+ transition: 0.25s ease-in-out;
609
+ }
610
+
611
+ .footer img {
612
+ filter: grayscale(0.5);
613
+ transition: 0.25s ease-in-out;
614
+ }
615
+
616
+ .footer:hover {
617
+ opacity: 1;
618
+ }
619
+ .footer:hover img {
620
+ filter: grayscale(0);
621
+ }
622
+
623
+ @media (max-width: 768px) {
624
+ body {
625
+ padding: 15px;
626
+ }
627
+ .error-container {
628
+ padding: 32px;
629
+ }
630
+ .status-code {
631
+ font-size: 4rem;
632
+ }
633
+ .status-text {
634
+ font-size: 1.8rem;
635
+ }
636
+ .error-message {
637
+ font-size: 1.1rem;
638
+ margin-bottom: 30px;
639
+ }
640
+ .json-details-container {
641
+ font-size: 0.85rem;
642
+ }
643
+ }
644
+
645
+ @media (max-width: 480px) {
646
+ body {
647
+ padding: 10px;
648
+ }
649
+ .error-container {
650
+ padding: 24px;
651
+ border-radius: 8px;
652
+ }
653
+ .status-code {
654
+ font-size: 3rem;
655
+ }
656
+ .status-text {
657
+ font-size: 1.5rem;
658
+ margin-bottom: 20px;
659
+ }
660
+ .error-message {
661
+ font-size: 1rem;
662
+ margin-bottom: 25px;
663
+ }
664
+ .json-details-container {
665
+ padding: 15px;
666
+ font-size: 0.8rem;
667
+ }
668
+ }
669
+ </style>
670
+ </head>
671
+ <body>
672
+ <div class="error-container">
673
+ <div id="statusCode" class="status-code">${icon} ${statusCode}</div>
674
+
675
+ <div id="statusText" class="status-text">${statusMessage}</div>
676
+
677
+ <p id="errorMessage" class="error-message">${message}</p>
678
+
679
+ <!-- prettier-ignore -->
680
+ <div class="json-details-container"style="display: ${details ? "block" : "none"};">
681
+ <p class="text-sm">Technical Details:</p>
682
+ <pre><code id="jsonDetails">${details}</code></pre>
683
+ </div>
684
+ <div class="footer">
685
+ Powered by
686
+ <a href="${link}" target="_blank">
687
+ <img height="20" alt="%{poweredBy}" src="${image}" />
688
+ </a>
689
+ v${version}
690
+ </div>
691
+ </div>
692
+ </body>
693
+ </html>
694
+ `;
695
+ }
696
+
341
697
  //#endregion
342
698
  //#region packages/event-http/src/errors/error-renderer.ts
343
- const preStyles = "font-family: monospace;width: 100%;max-width: 900px;padding: 10px;margin: 20px auto;border-radius: 8px;background-color: #494949;box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);";
699
+ let framework = {
700
+ version: "0.6.2",
701
+ poweredBy: `wooksjs`,
702
+ link: `https://wooks.moost.org/`,
703
+ image: `https://wooks.moost.org/wooks-full-logo.png`
704
+ };
705
+ /** Renders HTTP error responses in HTML, JSON, or plain text based on the Accept header. */
344
706
  var HttpErrorRenderer = class extends BaseHttpResponseRenderer {
707
+ constructor(opts) {
708
+ super();
709
+ this.opts = opts;
710
+ }
711
+ icons = {
712
+ 401: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
713
+ 403: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
714
+ 404: typeof _404_tl_default === "function" ? _404_tl_default({}) : "",
715
+ 500: typeof _500_tl_default === "function" ? _500_tl_default({}) : ""
716
+ };
717
+ static registerFramework(opts) {
718
+ framework = opts;
719
+ }
345
720
  renderHtml(response) {
346
721
  const data = response.body || {};
347
722
  response.setContentType("text/html");
348
- const keys = Object.keys(data).filter((key) => ![
349
- "statusCode",
350
- "error",
351
- "message"
352
- ].includes(key));
353
- return `<html style="background-color: #333; color: #bbb;"><head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head><body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center><center><h4>${data.message}</h1></center><hr color="#666"><center style="color: #666;"> Wooks v0.6.0 </center>${keys.length > 0 ? `<pre style="${preStyles}">${JSON.stringify({
354
- ...data,
355
- statusCode: void 0,
356
- message: void 0,
357
- error: void 0
358
- }, null, " ")}</pre>` : ""}</body></html>`;
723
+ const hasDetails = Object.keys(data).length > 3;
724
+ const icon = data.statusCode >= 500 ? this.icons[500] : this.icons[data.statusCode] || "";
725
+ return typeof error_tl_default === "function" ? error_tl_default({
726
+ icon,
727
+ statusCode: data.statusCode,
728
+ statusMessage: httpStatusCodes[data.statusCode],
729
+ message: data.message,
730
+ details: hasDetails ? JSON.stringify(data, null, " ") : "",
731
+ version: (this.opts || framework).version,
732
+ poweredBy: (this.opts || framework).poweredBy,
733
+ link: (this.opts || framework).link,
734
+ image: (this.opts || framework).image
735
+ }) : JSON.stringify(data, null, " ");
359
736
  }
360
737
  renderText(response) {
361
738
  const data = response.body || {};
@@ -397,6 +774,7 @@ function escapeQuotes(s) {
397
774
 
398
775
  //#endregion
399
776
  //#region packages/event-http/src/errors/http-error.ts
777
+ /** Represents an HTTP error with a status code and optional structured body. */
400
778
  var HttpError = class extends Error {
401
779
  name = "HttpError";
402
780
  constructor(code = 500, _body = "") {
@@ -428,15 +806,24 @@ var HttpError = class extends Error {
428
806
  //#endregion
429
807
  //#region packages/event-http/src/composables/request.ts
430
808
  const xForwardedFor = "x-forwarded-for";
809
+ /** Default safety limits for request body reading (size, ratio, timeout). */
431
810
  const DEFAULT_LIMITS = {
432
811
  maxCompressed: 1 * 1024 * 1024,
433
812
  maxInflated: 10 * 1024 * 1024,
434
813
  maxRatio: 100,
435
814
  readTimeoutMs: 1e4
436
815
  };
816
+ /**
817
+ * Provides access to the incoming HTTP request (method, url, headers, body, IP).
818
+ * @example
819
+ * ```ts
820
+ * const { method, url, rawBody, getIp } = useRequest()
821
+ * const body = await rawBody()
822
+ * ```
823
+ */
437
824
  function useRequest() {
438
825
  const { store } = useHttpContext();
439
- const { init, get, set } = store("request");
826
+ const { init } = store("request");
440
827
  const event = store("event");
441
828
  const req = event.get("req");
442
829
  const contentEncoding = req.headers["content-encoding"];
@@ -450,18 +837,33 @@ function useRequest() {
450
837
  ].includes(p)) return true;
451
838
  return false;
452
839
  });
453
- const getMaxCompressed = () => get("maxCompressed") ?? DEFAULT_LIMITS.maxCompressed;
454
- const setMaxCompressed = (limit) => set("maxCompressed", limit);
455
- const getReadTimeoutMs = () => get("readTimeoutMs") ?? DEFAULT_LIMITS.readTimeoutMs;
456
- const setReadTimeoutMs = (limit) => set("readTimeoutMs", limit);
457
- const getMaxInflated = () => get("maxInflated") ?? DEFAULT_LIMITS.maxInflated;
458
- const setMaxInflated = (limit) => set("maxInflated", limit);
840
+ const limits = () => event.get("requestLimits");
841
+ const setLimit = (key, value) => {
842
+ let obj = limits();
843
+ if (!obj?.perRequest) {
844
+ obj = {
845
+ ...obj,
846
+ perRequest: true
847
+ };
848
+ event.set("requestLimits", obj);
849
+ }
850
+ obj[key] = value;
851
+ };
852
+ const getMaxCompressed = () => limits()?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
853
+ const setMaxCompressed = (limit) => setLimit("maxCompressed", limit);
854
+ const getMaxInflated = () => limits()?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
855
+ const setMaxInflated = (limit) => setLimit("maxInflated", limit);
856
+ const getMaxRatio = () => limits()?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
857
+ const setMaxRatio = (limit) => setLimit("maxRatio", limit);
858
+ const getReadTimeoutMs = () => limits()?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
859
+ const setReadTimeoutMs = (limit) => setLimit("readTimeoutMs", limit);
459
860
  const rawBody = () => init("rawBody", async () => {
460
861
  const encs = contentEncodings();
461
862
  const isZip = isCompressed();
462
863
  const streamable = isZip && encodingSupportsStream(encs);
463
864
  const maxCompressed = getMaxCompressed();
464
865
  const maxInflated = getMaxInflated();
866
+ const maxRatio = getMaxRatio();
465
867
  const timeoutMs = getReadTimeoutMs();
466
868
  const cl = Number(req.headers["content-length"] ?? 0);
467
869
  const upfrontLimit = isZip ? maxCompressed : maxInflated;
@@ -519,6 +921,7 @@ function useRequest() {
519
921
  inflatedBytes = body.byteLength;
520
922
  if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
521
923
  }
924
+ if (isZip && rawBytes > 0 && inflatedBytes / rawBytes > maxRatio) throw new HttpError(413, "Compression ratio too high");
522
925
  return body;
523
926
  });
524
927
  const reqId = (0, __wooksjs_event_core.useEventId)().getId;
@@ -550,15 +953,32 @@ function useRequest() {
550
953
  getReadTimeoutMs,
551
954
  setReadTimeoutMs,
552
955
  getMaxInflated,
553
- setMaxInflated
956
+ setMaxInflated,
957
+ getMaxRatio,
958
+ setMaxRatio
554
959
  };
555
960
  }
556
961
 
557
962
  //#endregion
558
963
  //#region packages/event-http/src/composables/headers.ts
964
+ /**
965
+ * Returns the incoming request headers.
966
+ * @example
967
+ * ```ts
968
+ * const { host, authorization } = useHeaders()
969
+ * ```
970
+ */
559
971
  function useHeaders() {
560
972
  return useRequest().headers;
561
973
  }
974
+ /**
975
+ * Provides methods to set, get, and remove outgoing response headers.
976
+ * @example
977
+ * ```ts
978
+ * const { setHeader, setContentType, enableCors } = useSetHeaders()
979
+ * setHeader('x-request-id', '123')
980
+ * ```
981
+ */
562
982
  function useSetHeaders() {
563
983
  const { store } = useHttpContext();
564
984
  const setHeaderStore = store("setHeader");
@@ -580,6 +1000,7 @@ function useSetHeaders() {
580
1000
  enableCors
581
1001
  };
582
1002
  }
1003
+ /** Returns a hookable accessor for a single outgoing response header by name. */
583
1004
  function useSetHeader(name) {
584
1005
  const { store } = useHttpContext();
585
1006
  const { hook } = store("setHeader");
@@ -588,6 +1009,14 @@ function useSetHeader(name) {
588
1009
 
589
1010
  //#endregion
590
1011
  //#region packages/event-http/src/composables/cookies.ts
1012
+ /**
1013
+ * Provides access to parsed request cookies.
1014
+ * @example
1015
+ * ```ts
1016
+ * const { getCookie, rawCookies } = useCookies()
1017
+ * const sessionId = getCookie('session_id')
1018
+ * ```
1019
+ */
591
1020
  function useCookies() {
592
1021
  const { store } = useHttpContext();
593
1022
  const { cookie } = useHeaders();
@@ -603,6 +1032,7 @@ function useCookies() {
603
1032
  getCookie
604
1033
  };
605
1034
  }
1035
+ /** Provides methods to set, get, remove, and clear outgoing response cookies. */
606
1036
  function useSetCookies() {
607
1037
  const { store } = useHttpContext();
608
1038
  const cookiesStore = store("setCookies");
@@ -623,6 +1053,7 @@ function useSetCookies() {
623
1053
  cookies
624
1054
  };
625
1055
  }
1056
+ /** Returns a hookable accessor for a single outgoing cookie by name. */
626
1057
  function useSetCookie(name) {
627
1058
  const { setCookie, getCookie } = useSetCookies();
628
1059
  const valueHook = (0, __wooksjs_event_core.attachHook)({
@@ -644,6 +1075,7 @@ function useSetCookie(name) {
644
1075
 
645
1076
  //#endregion
646
1077
  //#region packages/event-http/src/composables/header-accept.ts
1078
+ /** Provides helpers to check the request's Accept header for supported MIME types. */
647
1079
  function useAccept() {
648
1080
  const { store } = useHttpContext();
649
1081
  const { accept } = useHeaders();
@@ -664,6 +1096,14 @@ function useAccept() {
664
1096
 
665
1097
  //#endregion
666
1098
  //#region packages/event-http/src/composables/header-authorization.ts
1099
+ /**
1100
+ * Provides parsed access to the Authorization header (type, credentials, Basic decoding).
1101
+ * @example
1102
+ * ```ts
1103
+ * const { isBearer, authRawCredentials, basicCredentials } = useAuthorization()
1104
+ * if (isBearer()) { const token = authRawCredentials() }
1105
+ * ```
1106
+ */
667
1107
  function useAuthorization() {
668
1108
  const { store } = useHttpContext();
669
1109
  const { authorization } = useHeaders();
@@ -736,6 +1176,7 @@ const cacheControlFunc = {
736
1176
  const renderAge = (v) => convertTime(v, "s").toString();
737
1177
  const renderExpires = (v) => typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString();
738
1178
  const renderPragmaNoCache = (v) => v ? "no-cache" : "";
1179
+ /** Provides helpers to set cache-related response headers (Cache-Control, Expires, Age, Pragma). */
739
1180
  function useSetCacheControl() {
740
1181
  const { setHeader } = useSetHeaders();
741
1182
  const setAge = (value) => {
@@ -760,6 +1201,14 @@ function useSetCacheControl() {
760
1201
 
761
1202
  //#endregion
762
1203
  //#region packages/event-http/src/composables/response.ts
1204
+ /**
1205
+ * Provides access to the raw HTTP response and status code management.
1206
+ * @example
1207
+ * ```ts
1208
+ * const { status, rawResponse, hasResponded } = useResponse()
1209
+ * status(200)
1210
+ * ```
1211
+ */
763
1212
  function useResponse() {
764
1213
  const { store } = useHttpContext();
765
1214
  const event = store("event");
@@ -782,6 +1231,7 @@ function useResponse() {
782
1231
  })
783
1232
  };
784
1233
  }
1234
+ /** Returns a hookable accessor for the response status code. */
785
1235
  function useStatus() {
786
1236
  const { store } = useHttpContext();
787
1237
  return store("status").hook("code");
@@ -789,6 +1239,11 @@ function useStatus() {
789
1239
 
790
1240
  //#endregion
791
1241
  //#region packages/event-http/src/utils/url-search-params.ts
1242
+ const ILLEGAL_KEYS = new Set([
1243
+ "__proto__",
1244
+ "constructor",
1245
+ "prototype"
1246
+ ]);
792
1247
  var WooksURLSearchParams = class extends url.URLSearchParams {
793
1248
  toJson() {
794
1249
  const json = Object.create(null);
@@ -796,7 +1251,7 @@ var WooksURLSearchParams = class extends url.URLSearchParams {
796
1251
  const a = json[key] = json[key] || [];
797
1252
  a.push(value);
798
1253
  } else {
799
- if (key === "__proto__") throw new HttpError(400, `Illegal key name "${key}"`);
1254
+ if (ILLEGAL_KEYS.has(key)) throw new HttpError(400, `Illegal key name "${key}"`);
800
1255
  if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
801
1256
  json[key] = value;
802
1257
  }
@@ -809,6 +1264,14 @@ function isArrayParam(name) {
809
1264
 
810
1265
  //#endregion
811
1266
  //#region packages/event-http/src/composables/search-params.ts
1267
+ /**
1268
+ * Provides access to URL search (query) parameters from the request.
1269
+ * @example
1270
+ * ```ts
1271
+ * const { urlSearchParams, jsonSearchParams } = useSearchParams()
1272
+ * const page = urlSearchParams().get('page')
1273
+ * ```
1274
+ */
812
1275
  function useSearchParams() {
813
1276
  const { store } = useHttpContext();
814
1277
  const url$1 = useRequest().url || "";
@@ -983,7 +1446,7 @@ var BaseHttpResponse = class {
983
1446
  async function respondWithFetch(fetchBody, res) {
984
1447
  if (fetchBody) try {
985
1448
  for await (const chunk of fetchBody) res.write(chunk);
986
- } catch (error) {}
1449
+ } catch {}
987
1450
  res.end();
988
1451
  }
989
1452
 
@@ -1011,6 +1474,7 @@ function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRe
1011
1474
 
1012
1475
  //#endregion
1013
1476
  //#region packages/event-http/src/http-adapter.ts
1477
+ /** HTTP adapter for Wooks that provides route registration, server lifecycle, and request handling. */
1014
1478
  var WooksHttp = class extends wooks.WooksAdapterBase {
1015
1479
  logger;
1016
1480
  constructor(opts, wooks$1) {
@@ -1018,27 +1482,35 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
1018
1482
  this.opts = opts;
1019
1483
  this.logger = opts?.logger || this.getLogger(`[wooks-http]`);
1020
1484
  }
1485
+ /** Registers a handler for all HTTP methods on the given path. */
1021
1486
  all(path, handler) {
1022
1487
  return this.on("*", path, handler);
1023
1488
  }
1489
+ /** Registers a GET route handler. */
1024
1490
  get(path, handler) {
1025
1491
  return this.on("GET", path, handler);
1026
1492
  }
1493
+ /** Registers a POST route handler. */
1027
1494
  post(path, handler) {
1028
1495
  return this.on("POST", path, handler);
1029
1496
  }
1497
+ /** Registers a PUT route handler. */
1030
1498
  put(path, handler) {
1031
1499
  return this.on("PUT", path, handler);
1032
1500
  }
1501
+ /** Registers a PATCH route handler. */
1033
1502
  patch(path, handler) {
1034
1503
  return this.on("PATCH", path, handler);
1035
1504
  }
1505
+ /** Registers a DELETE route handler. */
1036
1506
  delete(path, handler) {
1037
1507
  return this.on("DELETE", path, handler);
1038
1508
  }
1509
+ /** Registers a HEAD route handler. */
1039
1510
  head(path, handler) {
1040
1511
  return this.on("HEAD", path, handler);
1041
1512
  }
1513
+ /** Registers an OPTIONS route handler. */
1042
1514
  options(path, handler) {
1043
1515
  return this.on("OPTIONS", path, handler);
1044
1516
  }
@@ -1096,8 +1568,8 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
1096
1568
  }
1097
1569
  responder = createWooksResponder();
1098
1570
  respond(data) {
1099
- this.responder.respond(data)?.catch((e) => {
1100
- this.logger.error("Uncaught response exception", e);
1571
+ this.responder.respond(data)?.catch((error) => {
1572
+ this.logger.error("Uncaught response exception", error);
1101
1573
  });
1102
1574
  }
1103
1575
  /**
@@ -1116,7 +1588,8 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
1116
1588
  return (req, res) => {
1117
1589
  const runInContext = createHttpContext({
1118
1590
  req,
1119
- res
1591
+ res,
1592
+ requestLimits: this.opts?.requestLimits
1120
1593
  }, this.mergeEventOptions(this.opts?.eventOptions));
1121
1594
  runInContext(async () => {
1122
1595
  const { handlers } = this.wooks.lookup(req.method, req.url);
@@ -1156,10 +1629,13 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
1156
1629
  }
1157
1630
  };
1158
1631
  /**
1159
- * Factory for WooksHttp App
1160
- * @param opts TWooksHttpOptions
1161
- * @param wooks Wooks | WooksAdapterBase
1162
- * @returns WooksHttp
1632
+ * Creates a new WooksHttp application instance.
1633
+ * @example
1634
+ * ```ts
1635
+ * const app = createHttpApp()
1636
+ * app.get('/hello', () => 'Hello World!')
1637
+ * app.listen(3000)
1638
+ * ```
1163
1639
  */
1164
1640
  function createHttpApp(opts, wooks$1) {
1165
1641
  return new WooksHttp(opts, wooks$1);