@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/README.md +24 -0
- package/dist/index.cjs +510 -34
- package/dist/index.d.ts +134 -12
- package/dist/index.mjs +510 -34
- package/package.json +45 -37
- package/scripts/setup-skills.js +70 -0
- package/skills/wooksjs-event-http/SKILL.md +37 -0
- package/skills/wooksjs-event-http/addons.md +307 -0
- package/skills/wooksjs-event-http/core.md +297 -0
- package/skills/wooksjs-event-http/error-handling.md +253 -0
- package/skills/wooksjs-event-http/event-core.md +562 -0
- package/skills/wooksjs-event-http/request.md +220 -0
- package/skills/wooksjs-event-http/response.md +336 -0
- package/skills/wooksjs-event-http/routing.md +412 -0
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
|
|
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().
|
|
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).
|
|
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
|
-
|
|
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
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
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
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
|
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
|
|
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(`[96m[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((
|
|
1100
|
-
this.logger.error("Uncaught response exception",
|
|
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
|
-
*
|
|
1160
|
-
* @
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
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);
|