@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.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { WooksAdapterBase } from "wooks";
|
|
|
9
9
|
import { Readable as Readable$1 } from "stream";
|
|
10
10
|
|
|
11
11
|
//#region packages/event-http/src/event-http.ts
|
|
12
|
+
/** Creates an async event context for an incoming HTTP request/response pair. */
|
|
12
13
|
function createHttpContext(data, options) {
|
|
13
14
|
return createAsyncEventContext({
|
|
14
15
|
event: {
|
|
@@ -34,7 +35,7 @@ function escapeRegex(s) {
|
|
|
34
35
|
function safeDecode(f, v) {
|
|
35
36
|
try {
|
|
36
37
|
return f(v);
|
|
37
|
-
} catch
|
|
38
|
+
} catch {
|
|
38
39
|
return v;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -66,7 +67,12 @@ const units = {
|
|
|
66
67
|
|
|
67
68
|
//#endregion
|
|
68
69
|
//#region packages/event-http/src/utils/set-cookie.ts
|
|
70
|
+
const COOKIE_NAME_RE = /^[\w!#$%&'*+\-.^`|~]+$/;
|
|
71
|
+
function sanitizeCookieAttrValue(v) {
|
|
72
|
+
return v.replace(/[;\r\n]/g, "");
|
|
73
|
+
}
|
|
69
74
|
function renderCookie(key, data) {
|
|
75
|
+
if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
|
|
70
76
|
let attrs = "";
|
|
71
77
|
for (const [a, v] of Object.entries(data.attrs)) {
|
|
72
78
|
const func = cookieAttrFunc[a];
|
|
@@ -80,8 +86,8 @@ function renderCookie(key, data) {
|
|
|
80
86
|
const cookieAttrFunc = {
|
|
81
87
|
expires: (v) => `Expires=${typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString()}`,
|
|
82
88
|
maxAge: (v) => `Max-Age=${convertTime(v, "s").toString()}`,
|
|
83
|
-
domain: (v) => `Domain=${v}`,
|
|
84
|
-
path: (v) => `Path=${v}`,
|
|
89
|
+
domain: (v) => `Domain=${sanitizeCookieAttrValue(String(v))}`,
|
|
90
|
+
path: (v) => `Path=${sanitizeCookieAttrValue(String(v))}`,
|
|
85
91
|
secure: (v) => v ? "Secure" : "",
|
|
86
92
|
httpOnly: (v) => v ? "HttpOnly" : "",
|
|
87
93
|
sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
|
|
@@ -102,7 +108,7 @@ function encodingSupportsStream(encodings) {
|
|
|
102
108
|
}
|
|
103
109
|
async function uncompressBody(encodings, compressed) {
|
|
104
110
|
let buf = compressed;
|
|
105
|
-
for (const enc of encodings.slice().
|
|
111
|
+
for (const enc of encodings.slice().toReversed()) {
|
|
106
112
|
const c = compressors[enc];
|
|
107
113
|
if (!c) throw new Error(`Unsupported compression type "${enc}".`);
|
|
108
114
|
buf = await c.uncompress(buf);
|
|
@@ -112,7 +118,7 @@ async function uncompressBody(encodings, compressed) {
|
|
|
112
118
|
async function uncompressBodyStream(encodings, src) {
|
|
113
119
|
if (!encodingSupportsStream(encodings)) throw new Error("Some encodings lack a streaming decompressor");
|
|
114
120
|
let out = src;
|
|
115
|
-
for (const enc of Array.from(encodings).
|
|
121
|
+
for (const enc of Array.from(encodings).toReversed()) out = await compressors[enc].stream.uncompress(out);
|
|
116
122
|
return out;
|
|
117
123
|
}
|
|
118
124
|
|
|
@@ -315,24 +321,395 @@ let EHttpStatusCode = /* @__PURE__ */ function(EHttpStatusCode$1) {
|
|
|
315
321
|
return EHttpStatusCode$1;
|
|
316
322
|
}({});
|
|
317
323
|
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region packages/event-http/src/errors/403.tl.svg
|
|
326
|
+
function _403_tl_default(ctx) {
|
|
327
|
+
return `<svg height="64" viewBox="0 4 100 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#888888" stroke-width="2">
|
|
328
|
+
<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">
|
|
329
|
+
<animate attributeName="fill" dur="2s" repeatCount="indefinite"
|
|
330
|
+
values="#ff000000;#ff000050;#ff000000" />
|
|
331
|
+
</path>
|
|
332
|
+
|
|
333
|
+
<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" />
|
|
334
|
+
|
|
335
|
+
<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" />
|
|
336
|
+
</svg>
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region packages/event-http/src/errors/404.tl.svg
|
|
342
|
+
function _404_tl_default(ctx) {
|
|
343
|
+
return `<svg height="64" viewBox="0 20 100 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
344
|
+
<defs>
|
|
345
|
+
<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"/>
|
|
346
|
+
</defs>
|
|
347
|
+
|
|
348
|
+
<g id="queue" transform="translate(-5 -10)">
|
|
349
|
+
<use href="#sheet" opacity="0">
|
|
350
|
+
<animateTransform attributeName="transform" type="translate"
|
|
351
|
+
dur="3s" repeatCount="indefinite"
|
|
352
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
353
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
354
|
+
<animate attributeName="opacity"
|
|
355
|
+
dur="3s" repeatCount="indefinite"
|
|
356
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
357
|
+
values="0;1;1;0;0" />
|
|
358
|
+
</use>
|
|
359
|
+
|
|
360
|
+
<use href="#sheet" opacity="0">
|
|
361
|
+
<animateTransform attributeName="transform" type="translate"
|
|
362
|
+
dur="3s" begin="1s" repeatCount="indefinite"
|
|
363
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
364
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
365
|
+
<animate attributeName="opacity"
|
|
366
|
+
dur="3s" begin="1s" repeatCount="indefinite"
|
|
367
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
368
|
+
values="0;1;1;0;0" />
|
|
369
|
+
</use>
|
|
370
|
+
|
|
371
|
+
<use href="#sheet" opacity="0">
|
|
372
|
+
<animateTransform attributeName="transform" type="translate"
|
|
373
|
+
dur="3s" begin="2s" repeatCount="indefinite"
|
|
374
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
375
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
376
|
+
<animate attributeName="opacity"
|
|
377
|
+
dur="3s" begin="2s" repeatCount="indefinite"
|
|
378
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
379
|
+
values="0;1;1;0;0" />
|
|
380
|
+
</use>
|
|
381
|
+
</g>
|
|
382
|
+
|
|
383
|
+
<g>
|
|
384
|
+
<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"/>
|
|
385
|
+
|
|
386
|
+
<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"/>
|
|
387
|
+
</g>
|
|
388
|
+
</svg>
|
|
389
|
+
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region packages/event-http/src/errors/500.tl.svg
|
|
395
|
+
function _500_tl_default(ctx) {
|
|
396
|
+
return `<svg height="64" viewBox="0 0 120 100" xmlns="http://www.w3.org/2000/svg">
|
|
397
|
+
|
|
398
|
+
<g id="server">
|
|
399
|
+
|
|
400
|
+
<g fill="#88888888" stroke="#88888888" stroke-width="2" >
|
|
401
|
+
<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" />
|
|
402
|
+
</g>
|
|
403
|
+
<circle cx="30" cy="60" r="6" fill="red">
|
|
404
|
+
<animate attributeName="fill" dur="0.8s"
|
|
405
|
+
values="red;#2d0000;red" repeatCount="indefinite"/>
|
|
406
|
+
</circle>
|
|
407
|
+
|
|
408
|
+
</g>
|
|
409
|
+
|
|
410
|
+
<g fill="lightgray" opacity="0.75">
|
|
411
|
+
<circle cx="50" cy="35" r="6">
|
|
412
|
+
<animate attributeName="cy" from="35" to="15" dur="2s"
|
|
413
|
+
repeatCount="indefinite"/>
|
|
414
|
+
<animate attributeName="opacity" values="0.75;0" dur="2s"
|
|
415
|
+
repeatCount="indefinite"/>
|
|
416
|
+
</circle>
|
|
417
|
+
<circle cx="60" cy="40" r="4">
|
|
418
|
+
<animate attributeName="cy" from="40" to="20" dur="2s"
|
|
419
|
+
begin="0.4s" repeatCount="indefinite"/>
|
|
420
|
+
<animate attributeName="opacity" values="0.75;0" dur="2s"
|
|
421
|
+
begin="0.4s" repeatCount="indefinite"/>
|
|
422
|
+
</circle>
|
|
423
|
+
</g>
|
|
424
|
+
|
|
425
|
+
</svg>
|
|
426
|
+
`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region packages/event-http/src/errors/error.tl.html
|
|
431
|
+
function error_tl_default(ctx) {
|
|
432
|
+
const { statusCode, statusMessage, icon, message, details, link, image, version } = ctx;
|
|
433
|
+
return `<!doctype html>
|
|
434
|
+
<html lang="en">
|
|
435
|
+
<head>
|
|
436
|
+
<meta charset="UTF-8" />
|
|
437
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
438
|
+
<title>${statusCode} ${statusMessage}</title>
|
|
439
|
+
<style>
|
|
440
|
+
body {
|
|
441
|
+
font-family:
|
|
442
|
+
-apple-system, BlinkMacMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
|
443
|
+
'Open Sans', 'Helvetica Neue', sans-serif;
|
|
444
|
+
display: flex;
|
|
445
|
+
justify-content: center;
|
|
446
|
+
align-items: flex-start;
|
|
447
|
+
min-height: 100vh;
|
|
448
|
+
margin: 0;
|
|
449
|
+
padding: 0 20px;
|
|
450
|
+
box-sizing: border-box;
|
|
451
|
+
transition:
|
|
452
|
+
background-color 0.3s ease,
|
|
453
|
+
color 0.3s ease;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.error-container {
|
|
457
|
+
padding: 48px;
|
|
458
|
+
padding-bottom: 12px !important;
|
|
459
|
+
background-color: #ffffff;
|
|
460
|
+
border-radius: 0 0 12px 12px;
|
|
461
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
|
462
|
+
text-align: center;
|
|
463
|
+
max-width: 650px;
|
|
464
|
+
width: 100%;
|
|
465
|
+
transition:
|
|
466
|
+
background-color 0.3s ease,
|
|
467
|
+
border-color 0.3s ease,
|
|
468
|
+
box-shadow 0.3s ease;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.status-code {
|
|
472
|
+
font-size: 5rem;
|
|
473
|
+
font-weight: 900;
|
|
474
|
+
margin-bottom: 5px;
|
|
475
|
+
line-height: 1;
|
|
476
|
+
transition: color 0.3s ease;
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
justify-content: center;
|
|
480
|
+
gap: 1rem;
|
|
481
|
+
position: relative;
|
|
482
|
+
margin-right: 24px;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.status-text {
|
|
486
|
+
font-size: 2.25rem;
|
|
487
|
+
font-weight: 700;
|
|
488
|
+
margin-bottom: 25px;
|
|
489
|
+
transition: color 0.3s ease;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.error-message {
|
|
493
|
+
font-size: 1.25rem;
|
|
494
|
+
margin-bottom: 40px;
|
|
495
|
+
line-height: 1.7;
|
|
496
|
+
transition: color 0.3s ease;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.json-details-container {
|
|
500
|
+
padding: 20px;
|
|
501
|
+
border-radius: 8px;
|
|
502
|
+
text-align: left;
|
|
503
|
+
overflow-x: auto;
|
|
504
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
|
505
|
+
font-size: 0.9rem;
|
|
506
|
+
border: 1px solid;
|
|
507
|
+
transition:
|
|
508
|
+
background-color 0.3s ease,
|
|
509
|
+
color 0.3s ease,
|
|
510
|
+
border-color 0.3s ease;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.json-details-container pre {
|
|
514
|
+
margin: 0;
|
|
515
|
+
white-space: pre-wrap;
|
|
516
|
+
word-break: break-all;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.json-details-container code {
|
|
520
|
+
display: block;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
body {
|
|
524
|
+
background-color: #f8fafc;
|
|
525
|
+
color: #1f2937;
|
|
526
|
+
}
|
|
527
|
+
.error-container {
|
|
528
|
+
background-color: #ffffff;
|
|
529
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
|
530
|
+
}
|
|
531
|
+
.status-code {
|
|
532
|
+
color: #dc2626;
|
|
533
|
+
}
|
|
534
|
+
.status-text {
|
|
535
|
+
color: #1f2937;
|
|
536
|
+
}
|
|
537
|
+
.error-message {
|
|
538
|
+
color: #4b5563;
|
|
539
|
+
}
|
|
540
|
+
.json-details-container {
|
|
541
|
+
background-color: #f0f4f8;
|
|
542
|
+
color: #374151;
|
|
543
|
+
border-color: #d1d5db;
|
|
544
|
+
}
|
|
545
|
+
.json-details-container p {
|
|
546
|
+
color: #6b7280;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@media (prefers-color-scheme: dark) {
|
|
550
|
+
body {
|
|
551
|
+
background-color: #2d3748;
|
|
552
|
+
color: #e2e8f0;
|
|
553
|
+
}
|
|
554
|
+
.error-container {
|
|
555
|
+
background-color: #1a202c;
|
|
556
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
|
557
|
+
}
|
|
558
|
+
.status-code {
|
|
559
|
+
color: #f56565;
|
|
560
|
+
}
|
|
561
|
+
.status-text {
|
|
562
|
+
color: #cbd5e1;
|
|
563
|
+
}
|
|
564
|
+
.error-message {
|
|
565
|
+
color: #a0aec0;
|
|
566
|
+
}
|
|
567
|
+
.json-details-container {
|
|
568
|
+
background-color: #2d3748;
|
|
569
|
+
color: #e2e8f0;
|
|
570
|
+
border-color: #4a5568;
|
|
571
|
+
}
|
|
572
|
+
.json-details-container p {
|
|
573
|
+
color: #a0aec0;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.footer {
|
|
578
|
+
display: flex;
|
|
579
|
+
gap: 0.25rem;
|
|
580
|
+
font-size: 0.6em;
|
|
581
|
+
justify-content: flex-end;
|
|
582
|
+
align-items: center;
|
|
583
|
+
margin-top: 12px;
|
|
584
|
+
opacity: 0.5;
|
|
585
|
+
transition: 0.25s ease-in-out;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.footer img {
|
|
589
|
+
filter: grayscale(0.5);
|
|
590
|
+
transition: 0.25s ease-in-out;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.footer:hover {
|
|
594
|
+
opacity: 1;
|
|
595
|
+
}
|
|
596
|
+
.footer:hover img {
|
|
597
|
+
filter: grayscale(0);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@media (max-width: 768px) {
|
|
601
|
+
body {
|
|
602
|
+
padding: 15px;
|
|
603
|
+
}
|
|
604
|
+
.error-container {
|
|
605
|
+
padding: 32px;
|
|
606
|
+
}
|
|
607
|
+
.status-code {
|
|
608
|
+
font-size: 4rem;
|
|
609
|
+
}
|
|
610
|
+
.status-text {
|
|
611
|
+
font-size: 1.8rem;
|
|
612
|
+
}
|
|
613
|
+
.error-message {
|
|
614
|
+
font-size: 1.1rem;
|
|
615
|
+
margin-bottom: 30px;
|
|
616
|
+
}
|
|
617
|
+
.json-details-container {
|
|
618
|
+
font-size: 0.85rem;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
@media (max-width: 480px) {
|
|
623
|
+
body {
|
|
624
|
+
padding: 10px;
|
|
625
|
+
}
|
|
626
|
+
.error-container {
|
|
627
|
+
padding: 24px;
|
|
628
|
+
border-radius: 8px;
|
|
629
|
+
}
|
|
630
|
+
.status-code {
|
|
631
|
+
font-size: 3rem;
|
|
632
|
+
}
|
|
633
|
+
.status-text {
|
|
634
|
+
font-size: 1.5rem;
|
|
635
|
+
margin-bottom: 20px;
|
|
636
|
+
}
|
|
637
|
+
.error-message {
|
|
638
|
+
font-size: 1rem;
|
|
639
|
+
margin-bottom: 25px;
|
|
640
|
+
}
|
|
641
|
+
.json-details-container {
|
|
642
|
+
padding: 15px;
|
|
643
|
+
font-size: 0.8rem;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
</style>
|
|
647
|
+
</head>
|
|
648
|
+
<body>
|
|
649
|
+
<div class="error-container">
|
|
650
|
+
<div id="statusCode" class="status-code">${icon} ${statusCode}</div>
|
|
651
|
+
|
|
652
|
+
<div id="statusText" class="status-text">${statusMessage}</div>
|
|
653
|
+
|
|
654
|
+
<p id="errorMessage" class="error-message">${message}</p>
|
|
655
|
+
|
|
656
|
+
<!-- prettier-ignore -->
|
|
657
|
+
<div class="json-details-container"style="display: ${details ? "block" : "none"};">
|
|
658
|
+
<p class="text-sm">Technical Details:</p>
|
|
659
|
+
<pre><code id="jsonDetails">${details}</code></pre>
|
|
660
|
+
</div>
|
|
661
|
+
<div class="footer">
|
|
662
|
+
Powered by
|
|
663
|
+
<a href="${link}" target="_blank">
|
|
664
|
+
<img height="20" alt="%{poweredBy}" src="${image}" />
|
|
665
|
+
</a>
|
|
666
|
+
v${version}
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
</body>
|
|
670
|
+
</html>
|
|
671
|
+
`;
|
|
672
|
+
}
|
|
673
|
+
|
|
318
674
|
//#endregion
|
|
319
675
|
//#region packages/event-http/src/errors/error-renderer.ts
|
|
320
|
-
|
|
676
|
+
let framework = {
|
|
677
|
+
version: "0.6.2",
|
|
678
|
+
poweredBy: `wooksjs`,
|
|
679
|
+
link: `https://wooks.moost.org/`,
|
|
680
|
+
image: `https://wooks.moost.org/wooks-full-logo.png`
|
|
681
|
+
};
|
|
682
|
+
/** Renders HTTP error responses in HTML, JSON, or plain text based on the Accept header. */
|
|
321
683
|
var HttpErrorRenderer = class extends BaseHttpResponseRenderer {
|
|
684
|
+
constructor(opts) {
|
|
685
|
+
super();
|
|
686
|
+
this.opts = opts;
|
|
687
|
+
}
|
|
688
|
+
icons = {
|
|
689
|
+
401: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
690
|
+
403: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
691
|
+
404: typeof _404_tl_default === "function" ? _404_tl_default({}) : "",
|
|
692
|
+
500: typeof _500_tl_default === "function" ? _500_tl_default({}) : ""
|
|
693
|
+
};
|
|
694
|
+
static registerFramework(opts) {
|
|
695
|
+
framework = opts;
|
|
696
|
+
}
|
|
322
697
|
renderHtml(response) {
|
|
323
698
|
const data = response.body || {};
|
|
324
699
|
response.setContentType("text/html");
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
700
|
+
const hasDetails = Object.keys(data).length > 3;
|
|
701
|
+
const icon = data.statusCode >= 500 ? this.icons[500] : this.icons[data.statusCode] || "";
|
|
702
|
+
return typeof error_tl_default === "function" ? error_tl_default({
|
|
703
|
+
icon,
|
|
704
|
+
statusCode: data.statusCode,
|
|
705
|
+
statusMessage: httpStatusCodes[data.statusCode],
|
|
706
|
+
message: data.message,
|
|
707
|
+
details: hasDetails ? JSON.stringify(data, null, " ") : "",
|
|
708
|
+
version: (this.opts || framework).version,
|
|
709
|
+
poweredBy: (this.opts || framework).poweredBy,
|
|
710
|
+
link: (this.opts || framework).link,
|
|
711
|
+
image: (this.opts || framework).image
|
|
712
|
+
}) : JSON.stringify(data, null, " ");
|
|
336
713
|
}
|
|
337
714
|
renderText(response) {
|
|
338
715
|
const data = response.body || {};
|
|
@@ -374,6 +751,7 @@ function escapeQuotes(s) {
|
|
|
374
751
|
|
|
375
752
|
//#endregion
|
|
376
753
|
//#region packages/event-http/src/errors/http-error.ts
|
|
754
|
+
/** Represents an HTTP error with a status code and optional structured body. */
|
|
377
755
|
var HttpError = class extends Error {
|
|
378
756
|
name = "HttpError";
|
|
379
757
|
constructor(code = 500, _body = "") {
|
|
@@ -405,15 +783,24 @@ var HttpError = class extends Error {
|
|
|
405
783
|
//#endregion
|
|
406
784
|
//#region packages/event-http/src/composables/request.ts
|
|
407
785
|
const xForwardedFor = "x-forwarded-for";
|
|
786
|
+
/** Default safety limits for request body reading (size, ratio, timeout). */
|
|
408
787
|
const DEFAULT_LIMITS = {
|
|
409
788
|
maxCompressed: 1 * 1024 * 1024,
|
|
410
789
|
maxInflated: 10 * 1024 * 1024,
|
|
411
790
|
maxRatio: 100,
|
|
412
791
|
readTimeoutMs: 1e4
|
|
413
792
|
};
|
|
793
|
+
/**
|
|
794
|
+
* Provides access to the incoming HTTP request (method, url, headers, body, IP).
|
|
795
|
+
* @example
|
|
796
|
+
* ```ts
|
|
797
|
+
* const { method, url, rawBody, getIp } = useRequest()
|
|
798
|
+
* const body = await rawBody()
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
414
801
|
function useRequest() {
|
|
415
802
|
const { store } = useHttpContext();
|
|
416
|
-
const { init
|
|
803
|
+
const { init } = store("request");
|
|
417
804
|
const event = store("event");
|
|
418
805
|
const req = event.get("req");
|
|
419
806
|
const contentEncoding = req.headers["content-encoding"];
|
|
@@ -427,18 +814,33 @@ function useRequest() {
|
|
|
427
814
|
].includes(p)) return true;
|
|
428
815
|
return false;
|
|
429
816
|
});
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
817
|
+
const limits = () => event.get("requestLimits");
|
|
818
|
+
const setLimit = (key, value) => {
|
|
819
|
+
let obj = limits();
|
|
820
|
+
if (!obj?.perRequest) {
|
|
821
|
+
obj = {
|
|
822
|
+
...obj,
|
|
823
|
+
perRequest: true
|
|
824
|
+
};
|
|
825
|
+
event.set("requestLimits", obj);
|
|
826
|
+
}
|
|
827
|
+
obj[key] = value;
|
|
828
|
+
};
|
|
829
|
+
const getMaxCompressed = () => limits()?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
|
|
830
|
+
const setMaxCompressed = (limit) => setLimit("maxCompressed", limit);
|
|
831
|
+
const getMaxInflated = () => limits()?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
|
|
832
|
+
const setMaxInflated = (limit) => setLimit("maxInflated", limit);
|
|
833
|
+
const getMaxRatio = () => limits()?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
|
|
834
|
+
const setMaxRatio = (limit) => setLimit("maxRatio", limit);
|
|
835
|
+
const getReadTimeoutMs = () => limits()?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
|
|
836
|
+
const setReadTimeoutMs = (limit) => setLimit("readTimeoutMs", limit);
|
|
436
837
|
const rawBody = () => init("rawBody", async () => {
|
|
437
838
|
const encs = contentEncodings();
|
|
438
839
|
const isZip = isCompressed();
|
|
439
840
|
const streamable = isZip && encodingSupportsStream(encs);
|
|
440
841
|
const maxCompressed = getMaxCompressed();
|
|
441
842
|
const maxInflated = getMaxInflated();
|
|
843
|
+
const maxRatio = getMaxRatio();
|
|
442
844
|
const timeoutMs = getReadTimeoutMs();
|
|
443
845
|
const cl = Number(req.headers["content-length"] ?? 0);
|
|
444
846
|
const upfrontLimit = isZip ? maxCompressed : maxInflated;
|
|
@@ -496,6 +898,7 @@ function useRequest() {
|
|
|
496
898
|
inflatedBytes = body.byteLength;
|
|
497
899
|
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
|
|
498
900
|
}
|
|
901
|
+
if (isZip && rawBytes > 0 && inflatedBytes / rawBytes > maxRatio) throw new HttpError(413, "Compression ratio too high");
|
|
499
902
|
return body;
|
|
500
903
|
});
|
|
501
904
|
const reqId = useEventId().getId;
|
|
@@ -527,15 +930,32 @@ function useRequest() {
|
|
|
527
930
|
getReadTimeoutMs,
|
|
528
931
|
setReadTimeoutMs,
|
|
529
932
|
getMaxInflated,
|
|
530
|
-
setMaxInflated
|
|
933
|
+
setMaxInflated,
|
|
934
|
+
getMaxRatio,
|
|
935
|
+
setMaxRatio
|
|
531
936
|
};
|
|
532
937
|
}
|
|
533
938
|
|
|
534
939
|
//#endregion
|
|
535
940
|
//#region packages/event-http/src/composables/headers.ts
|
|
941
|
+
/**
|
|
942
|
+
* Returns the incoming request headers.
|
|
943
|
+
* @example
|
|
944
|
+
* ```ts
|
|
945
|
+
* const { host, authorization } = useHeaders()
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
536
948
|
function useHeaders() {
|
|
537
949
|
return useRequest().headers;
|
|
538
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Provides methods to set, get, and remove outgoing response headers.
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* const { setHeader, setContentType, enableCors } = useSetHeaders()
|
|
956
|
+
* setHeader('x-request-id', '123')
|
|
957
|
+
* ```
|
|
958
|
+
*/
|
|
539
959
|
function useSetHeaders() {
|
|
540
960
|
const { store } = useHttpContext();
|
|
541
961
|
const setHeaderStore = store("setHeader");
|
|
@@ -557,6 +977,7 @@ function useSetHeaders() {
|
|
|
557
977
|
enableCors
|
|
558
978
|
};
|
|
559
979
|
}
|
|
980
|
+
/** Returns a hookable accessor for a single outgoing response header by name. */
|
|
560
981
|
function useSetHeader(name) {
|
|
561
982
|
const { store } = useHttpContext();
|
|
562
983
|
const { hook } = store("setHeader");
|
|
@@ -565,6 +986,14 @@ function useSetHeader(name) {
|
|
|
565
986
|
|
|
566
987
|
//#endregion
|
|
567
988
|
//#region packages/event-http/src/composables/cookies.ts
|
|
989
|
+
/**
|
|
990
|
+
* Provides access to parsed request cookies.
|
|
991
|
+
* @example
|
|
992
|
+
* ```ts
|
|
993
|
+
* const { getCookie, rawCookies } = useCookies()
|
|
994
|
+
* const sessionId = getCookie('session_id')
|
|
995
|
+
* ```
|
|
996
|
+
*/
|
|
568
997
|
function useCookies() {
|
|
569
998
|
const { store } = useHttpContext();
|
|
570
999
|
const { cookie } = useHeaders();
|
|
@@ -580,6 +1009,7 @@ function useCookies() {
|
|
|
580
1009
|
getCookie
|
|
581
1010
|
};
|
|
582
1011
|
}
|
|
1012
|
+
/** Provides methods to set, get, remove, and clear outgoing response cookies. */
|
|
583
1013
|
function useSetCookies() {
|
|
584
1014
|
const { store } = useHttpContext();
|
|
585
1015
|
const cookiesStore = store("setCookies");
|
|
@@ -600,6 +1030,7 @@ function useSetCookies() {
|
|
|
600
1030
|
cookies
|
|
601
1031
|
};
|
|
602
1032
|
}
|
|
1033
|
+
/** Returns a hookable accessor for a single outgoing cookie by name. */
|
|
603
1034
|
function useSetCookie(name) {
|
|
604
1035
|
const { setCookie, getCookie } = useSetCookies();
|
|
605
1036
|
const valueHook = attachHook({
|
|
@@ -621,6 +1052,7 @@ function useSetCookie(name) {
|
|
|
621
1052
|
|
|
622
1053
|
//#endregion
|
|
623
1054
|
//#region packages/event-http/src/composables/header-accept.ts
|
|
1055
|
+
/** Provides helpers to check the request's Accept header for supported MIME types. */
|
|
624
1056
|
function useAccept() {
|
|
625
1057
|
const { store } = useHttpContext();
|
|
626
1058
|
const { accept } = useHeaders();
|
|
@@ -641,6 +1073,14 @@ function useAccept() {
|
|
|
641
1073
|
|
|
642
1074
|
//#endregion
|
|
643
1075
|
//#region packages/event-http/src/composables/header-authorization.ts
|
|
1076
|
+
/**
|
|
1077
|
+
* Provides parsed access to the Authorization header (type, credentials, Basic decoding).
|
|
1078
|
+
* @example
|
|
1079
|
+
* ```ts
|
|
1080
|
+
* const { isBearer, authRawCredentials, basicCredentials } = useAuthorization()
|
|
1081
|
+
* if (isBearer()) { const token = authRawCredentials() }
|
|
1082
|
+
* ```
|
|
1083
|
+
*/
|
|
644
1084
|
function useAuthorization() {
|
|
645
1085
|
const { store } = useHttpContext();
|
|
646
1086
|
const { authorization } = useHeaders();
|
|
@@ -713,6 +1153,7 @@ const cacheControlFunc = {
|
|
|
713
1153
|
const renderAge = (v) => convertTime(v, "s").toString();
|
|
714
1154
|
const renderExpires = (v) => typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString();
|
|
715
1155
|
const renderPragmaNoCache = (v) => v ? "no-cache" : "";
|
|
1156
|
+
/** Provides helpers to set cache-related response headers (Cache-Control, Expires, Age, Pragma). */
|
|
716
1157
|
function useSetCacheControl() {
|
|
717
1158
|
const { setHeader } = useSetHeaders();
|
|
718
1159
|
const setAge = (value) => {
|
|
@@ -737,6 +1178,14 @@ function useSetCacheControl() {
|
|
|
737
1178
|
|
|
738
1179
|
//#endregion
|
|
739
1180
|
//#region packages/event-http/src/composables/response.ts
|
|
1181
|
+
/**
|
|
1182
|
+
* Provides access to the raw HTTP response and status code management.
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* const { status, rawResponse, hasResponded } = useResponse()
|
|
1186
|
+
* status(200)
|
|
1187
|
+
* ```
|
|
1188
|
+
*/
|
|
740
1189
|
function useResponse() {
|
|
741
1190
|
const { store } = useHttpContext();
|
|
742
1191
|
const event = store("event");
|
|
@@ -759,6 +1208,7 @@ function useResponse() {
|
|
|
759
1208
|
})
|
|
760
1209
|
};
|
|
761
1210
|
}
|
|
1211
|
+
/** Returns a hookable accessor for the response status code. */
|
|
762
1212
|
function useStatus() {
|
|
763
1213
|
const { store } = useHttpContext();
|
|
764
1214
|
return store("status").hook("code");
|
|
@@ -766,6 +1216,11 @@ function useStatus() {
|
|
|
766
1216
|
|
|
767
1217
|
//#endregion
|
|
768
1218
|
//#region packages/event-http/src/utils/url-search-params.ts
|
|
1219
|
+
const ILLEGAL_KEYS = new Set([
|
|
1220
|
+
"__proto__",
|
|
1221
|
+
"constructor",
|
|
1222
|
+
"prototype"
|
|
1223
|
+
]);
|
|
769
1224
|
var WooksURLSearchParams = class extends URLSearchParams {
|
|
770
1225
|
toJson() {
|
|
771
1226
|
const json = Object.create(null);
|
|
@@ -773,7 +1228,7 @@ var WooksURLSearchParams = class extends URLSearchParams {
|
|
|
773
1228
|
const a = json[key] = json[key] || [];
|
|
774
1229
|
a.push(value);
|
|
775
1230
|
} else {
|
|
776
|
-
if (key
|
|
1231
|
+
if (ILLEGAL_KEYS.has(key)) throw new HttpError(400, `Illegal key name "${key}"`);
|
|
777
1232
|
if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
|
|
778
1233
|
json[key] = value;
|
|
779
1234
|
}
|
|
@@ -786,6 +1241,14 @@ function isArrayParam(name) {
|
|
|
786
1241
|
|
|
787
1242
|
//#endregion
|
|
788
1243
|
//#region packages/event-http/src/composables/search-params.ts
|
|
1244
|
+
/**
|
|
1245
|
+
* Provides access to URL search (query) parameters from the request.
|
|
1246
|
+
* @example
|
|
1247
|
+
* ```ts
|
|
1248
|
+
* const { urlSearchParams, jsonSearchParams } = useSearchParams()
|
|
1249
|
+
* const page = urlSearchParams().get('page')
|
|
1250
|
+
* ```
|
|
1251
|
+
*/
|
|
789
1252
|
function useSearchParams() {
|
|
790
1253
|
const { store } = useHttpContext();
|
|
791
1254
|
const url = useRequest().url || "";
|
|
@@ -960,7 +1423,7 @@ var BaseHttpResponse = class {
|
|
|
960
1423
|
async function respondWithFetch(fetchBody, res) {
|
|
961
1424
|
if (fetchBody) try {
|
|
962
1425
|
for await (const chunk of fetchBody) res.write(chunk);
|
|
963
|
-
} catch
|
|
1426
|
+
} catch {}
|
|
964
1427
|
res.end();
|
|
965
1428
|
}
|
|
966
1429
|
|
|
@@ -988,6 +1451,7 @@ function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRe
|
|
|
988
1451
|
|
|
989
1452
|
//#endregion
|
|
990
1453
|
//#region packages/event-http/src/http-adapter.ts
|
|
1454
|
+
/** HTTP adapter for Wooks that provides route registration, server lifecycle, and request handling. */
|
|
991
1455
|
var WooksHttp = class extends WooksAdapterBase {
|
|
992
1456
|
logger;
|
|
993
1457
|
constructor(opts, wooks) {
|
|
@@ -995,27 +1459,35 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
995
1459
|
this.opts = opts;
|
|
996
1460
|
this.logger = opts?.logger || this.getLogger(`[96m[wooks-http]`);
|
|
997
1461
|
}
|
|
1462
|
+
/** Registers a handler for all HTTP methods on the given path. */
|
|
998
1463
|
all(path, handler) {
|
|
999
1464
|
return this.on("*", path, handler);
|
|
1000
1465
|
}
|
|
1466
|
+
/** Registers a GET route handler. */
|
|
1001
1467
|
get(path, handler) {
|
|
1002
1468
|
return this.on("GET", path, handler);
|
|
1003
1469
|
}
|
|
1470
|
+
/** Registers a POST route handler. */
|
|
1004
1471
|
post(path, handler) {
|
|
1005
1472
|
return this.on("POST", path, handler);
|
|
1006
1473
|
}
|
|
1474
|
+
/** Registers a PUT route handler. */
|
|
1007
1475
|
put(path, handler) {
|
|
1008
1476
|
return this.on("PUT", path, handler);
|
|
1009
1477
|
}
|
|
1478
|
+
/** Registers a PATCH route handler. */
|
|
1010
1479
|
patch(path, handler) {
|
|
1011
1480
|
return this.on("PATCH", path, handler);
|
|
1012
1481
|
}
|
|
1482
|
+
/** Registers a DELETE route handler. */
|
|
1013
1483
|
delete(path, handler) {
|
|
1014
1484
|
return this.on("DELETE", path, handler);
|
|
1015
1485
|
}
|
|
1486
|
+
/** Registers a HEAD route handler. */
|
|
1016
1487
|
head(path, handler) {
|
|
1017
1488
|
return this.on("HEAD", path, handler);
|
|
1018
1489
|
}
|
|
1490
|
+
/** Registers an OPTIONS route handler. */
|
|
1019
1491
|
options(path, handler) {
|
|
1020
1492
|
return this.on("OPTIONS", path, handler);
|
|
1021
1493
|
}
|
|
@@ -1073,8 +1545,8 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1073
1545
|
}
|
|
1074
1546
|
responder = createWooksResponder();
|
|
1075
1547
|
respond(data) {
|
|
1076
|
-
this.responder.respond(data)?.catch((
|
|
1077
|
-
this.logger.error("Uncaught response exception",
|
|
1548
|
+
this.responder.respond(data)?.catch((error) => {
|
|
1549
|
+
this.logger.error("Uncaught response exception", error);
|
|
1078
1550
|
});
|
|
1079
1551
|
}
|
|
1080
1552
|
/**
|
|
@@ -1093,7 +1565,8 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1093
1565
|
return (req, res) => {
|
|
1094
1566
|
const runInContext = createHttpContext({
|
|
1095
1567
|
req,
|
|
1096
|
-
res
|
|
1568
|
+
res,
|
|
1569
|
+
requestLimits: this.opts?.requestLimits
|
|
1097
1570
|
}, this.mergeEventOptions(this.opts?.eventOptions));
|
|
1098
1571
|
runInContext(async () => {
|
|
1099
1572
|
const { handlers } = this.wooks.lookup(req.method, req.url);
|
|
@@ -1133,10 +1606,13 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1133
1606
|
}
|
|
1134
1607
|
};
|
|
1135
1608
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
* @
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1609
|
+
* Creates a new WooksHttp application instance.
|
|
1610
|
+
* @example
|
|
1611
|
+
* ```ts
|
|
1612
|
+
* const app = createHttpApp()
|
|
1613
|
+
* app.get('/hello', () => 'Hello World!')
|
|
1614
|
+
* app.listen(3000)
|
|
1615
|
+
* ```
|
|
1140
1616
|
*/
|
|
1141
1617
|
function createHttpApp(opts, wooks) {
|
|
1142
1618
|
return new WooksHttp(opts, wooks);
|