@wooksjs/event-http 0.7.1 → 0.7.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 +493 -499
- package/dist/index.d.ts +10 -10
- package/dist/index.mjs +494 -500
- package/package.json +5 -5
- package/skills/wooksjs-event-http/request.md +7 -7
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { EventContext, cached, cachedBy, current, defineEventKind, defineWook, routeParamsKey, run, slot, useEventId, useLogger, useRouteParams } from "@wooksjs/event-core";
|
|
1
|
+
import { EventContext, cached, cachedBy, createEventContext, current, defineEventKind, defineWook, routeParamsKey, run, slot, useEventId, useLogger, useRouteParams } from "@wooksjs/event-core";
|
|
2
2
|
import { Buffer as Buffer$1 } from "buffer";
|
|
3
3
|
import { Readable, pipeline } from "node:stream";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { createBrotliCompress, createBrotliDecompress, createDeflate, createGunzip, createGzip, createInflate } from "node:zlib";
|
|
6
6
|
import { URLSearchParams } from "url";
|
|
7
|
-
import { Readable as Readable$1 } from "stream";
|
|
8
7
|
import http, { IncomingMessage, ServerResponse } from "http";
|
|
9
8
|
import { WooksAdapterBase } from "wooks";
|
|
9
|
+
import { Readable as Readable$1 } from "stream";
|
|
10
10
|
import { Socket } from "net";
|
|
11
11
|
|
|
12
12
|
//#region packages/event-http/src/http-kind.ts
|
|
@@ -635,492 +635,119 @@ const useUrlParams = defineWook((ctx) => ({
|
|
|
635
635
|
}));
|
|
636
636
|
|
|
637
637
|
//#endregion
|
|
638
|
-
//#region packages/event-http/src/
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return
|
|
638
|
+
//#region packages/event-http/src/event-http.ts
|
|
639
|
+
/** Creates an HTTP event context and runs `fn` inside it. */
|
|
640
|
+
function createHttpContext(options, seeds, fn) {
|
|
641
|
+
return createEventContext(options, httpKind, seeds, fn);
|
|
642
|
+
}
|
|
643
|
+
/** Returns the current HTTP event context. */
|
|
644
|
+
function useHttpContext(ctx) {
|
|
645
|
+
return ctx ?? current();
|
|
646
646
|
}
|
|
647
|
-
const units = {
|
|
648
|
-
ms: 1,
|
|
649
|
-
s: 1e3,
|
|
650
|
-
m: 1e3 * 60,
|
|
651
|
-
h: 1e3 * 60 * 60,
|
|
652
|
-
d: 1e3 * 60 * 60 * 24,
|
|
653
|
-
w: 1e3 * 60 * 60 * 24 * 7,
|
|
654
|
-
M: 1e3 * 60 * 60 * 24 * 30,
|
|
655
|
-
Y: 1e3 * 60 * 60 * 24 * 365
|
|
656
|
-
};
|
|
657
647
|
|
|
658
648
|
//#endregion
|
|
659
|
-
//#region packages/event-http/src/
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
649
|
+
//#region packages/event-http/src/errors/403.tl.svg
|
|
650
|
+
function _403_tl_default(ctx) {
|
|
651
|
+
return `<svg height="64" viewBox="0 4 100 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#888888" stroke-width="2">
|
|
652
|
+
<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">
|
|
653
|
+
<animate attributeName="fill" dur="2s" repeatCount="indefinite"
|
|
654
|
+
values="#ff000000;#ff000050;#ff000000" />
|
|
655
|
+
</path>
|
|
656
|
+
|
|
657
|
+
<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" />
|
|
658
|
+
|
|
659
|
+
<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" />
|
|
660
|
+
</svg>
|
|
661
|
+
`;
|
|
672
662
|
}
|
|
673
|
-
const cacheControlFunc = {
|
|
674
|
-
mustRevalidate: (v) => v ? "must-revalidate" : "",
|
|
675
|
-
noCache: (v) => v ? typeof v === "string" ? `no-cache="${v}"` : "no-cache" : "",
|
|
676
|
-
noStore: (v) => v ? "no-store" : "",
|
|
677
|
-
noTransform: (v) => v ? "no-transform" : "",
|
|
678
|
-
public: (v) => v ? "public" : "",
|
|
679
|
-
private: (v) => v ? typeof v === "string" ? `private="${v}"` : "private" : "",
|
|
680
|
-
proxyRevalidate: (v) => v ? "proxy-revalidate" : "",
|
|
681
|
-
maxAge: (v) => `max-age=${convertTime(v, "s").toString()}`,
|
|
682
|
-
sMaxage: (v) => `s-maxage=${convertTime(v, "s").toString()}`
|
|
683
|
-
};
|
|
684
663
|
|
|
685
664
|
//#endregion
|
|
686
|
-
//#region packages/event-http/src/
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
665
|
+
//#region packages/event-http/src/errors/404.tl.svg
|
|
666
|
+
function _404_tl_default(ctx) {
|
|
667
|
+
return `<svg height="64" viewBox="0 20 100 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
668
|
+
<defs>
|
|
669
|
+
<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"/>
|
|
670
|
+
</defs>
|
|
671
|
+
|
|
672
|
+
<g id="queue" transform="translate(-5 -10)">
|
|
673
|
+
<use href="#sheet" opacity="0">
|
|
674
|
+
<animateTransform attributeName="transform" type="translate"
|
|
675
|
+
dur="3s" repeatCount="indefinite"
|
|
676
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
677
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
678
|
+
<animate attributeName="opacity"
|
|
679
|
+
dur="3s" repeatCount="indefinite"
|
|
680
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
681
|
+
values="0;1;1;0;0" />
|
|
682
|
+
</use>
|
|
683
|
+
|
|
684
|
+
<use href="#sheet" opacity="0">
|
|
685
|
+
<animateTransform attributeName="transform" type="translate"
|
|
686
|
+
dur="3s" begin="1s" repeatCount="indefinite"
|
|
687
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
688
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
689
|
+
<animate attributeName="opacity"
|
|
690
|
+
dur="3s" begin="1s" repeatCount="indefinite"
|
|
691
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
692
|
+
values="0;1;1;0;0" />
|
|
693
|
+
</use>
|
|
694
|
+
|
|
695
|
+
<use href="#sheet" opacity="0">
|
|
696
|
+
<animateTransform attributeName="transform" type="translate"
|
|
697
|
+
dur="3s" begin="2s" repeatCount="indefinite"
|
|
698
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
699
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
700
|
+
<animate attributeName="opacity"
|
|
701
|
+
dur="3s" begin="2s" repeatCount="indefinite"
|
|
702
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
703
|
+
values="0;1;1;0;0" />
|
|
704
|
+
</use>
|
|
705
|
+
</g>
|
|
706
|
+
|
|
707
|
+
<g>
|
|
708
|
+
<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"/>
|
|
709
|
+
|
|
710
|
+
<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"/>
|
|
711
|
+
</g>
|
|
712
|
+
</svg>
|
|
713
|
+
|
|
714
|
+
`;
|
|
690
715
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
* All header mutations are accumulated in memory and flushed in a single `writeHead()` call
|
|
727
|
-
* when `send()` is invoked. Setter methods are chainable.
|
|
728
|
-
*
|
|
729
|
-
* @example
|
|
730
|
-
* ```ts
|
|
731
|
-
* const response = useResponse()
|
|
732
|
-
* response.setStatus(200).setHeader('x-custom', 'value')
|
|
733
|
-
* response.setCookie('session', 'abc', { httpOnly: true })
|
|
734
|
-
* ```
|
|
735
|
-
*/
|
|
736
|
-
var HttpResponse = class {
|
|
737
|
-
/**
|
|
738
|
-
* @param _res - The underlying Node.js `ServerResponse`.
|
|
739
|
-
* @param _req - The underlying Node.js `IncomingMessage`.
|
|
740
|
-
* @param _logger - Logger instance for error reporting.
|
|
741
|
-
* @param defaultHeaders - Optional headers to pre-populate on this response (e.g. from `securityHeaders()`).
|
|
742
|
-
*/
|
|
743
|
-
constructor(_res, _req, _logger, defaultHeaders) {
|
|
744
|
-
this._res = _res;
|
|
745
|
-
this._req = _req;
|
|
746
|
-
this._logger = _logger;
|
|
747
|
-
if (defaultHeaders) for (const key in defaultHeaders) this._headers[key] = defaultHeaders[key];
|
|
748
|
-
}
|
|
749
|
-
_status = 0;
|
|
750
|
-
_body = void 0;
|
|
751
|
-
_headers = {};
|
|
752
|
-
_cookies = {};
|
|
753
|
-
_rawCookies = [];
|
|
754
|
-
_hasCookies = false;
|
|
755
|
-
_responded = false;
|
|
756
|
-
/** The HTTP status code. If not set, it is inferred automatically when `send()` is called. */
|
|
757
|
-
get status() {
|
|
758
|
-
return this._status;
|
|
759
|
-
}
|
|
760
|
-
set status(value) {
|
|
761
|
-
this._status = value;
|
|
762
|
-
}
|
|
763
|
-
/** Sets the HTTP status code (chainable). */
|
|
764
|
-
setStatus(value) {
|
|
765
|
-
this._status = value;
|
|
766
|
-
return this;
|
|
767
|
-
}
|
|
768
|
-
/** The response body. Automatically serialized by `send()` (objects → JSON, strings → text). */
|
|
769
|
-
get body() {
|
|
770
|
-
return this._body;
|
|
771
|
-
}
|
|
772
|
-
set body(value) {
|
|
773
|
-
this._body = value;
|
|
774
|
-
}
|
|
775
|
-
/** Sets the response body (chainable). */
|
|
776
|
-
setBody(value) {
|
|
777
|
-
this._body = value;
|
|
778
|
-
return this;
|
|
779
|
-
}
|
|
780
|
-
/** Sets a single response header (chainable). Arrays produce multi-value headers. */
|
|
781
|
-
setHeader(name, value) {
|
|
782
|
-
this._headers[name] = Array.isArray(value) ? value : value.toString();
|
|
783
|
-
return this;
|
|
784
|
-
}
|
|
785
|
-
/** Batch-sets multiple response headers from a record (chainable). Existing keys are overwritten. */
|
|
786
|
-
setHeaders(headers) {
|
|
787
|
-
for (const key in headers) this._headers[key] = headers[key];
|
|
788
|
-
return this;
|
|
789
|
-
}
|
|
790
|
-
/** Returns the value of a response header, or `undefined` if not set. */
|
|
791
|
-
getHeader(name) {
|
|
792
|
-
return this._headers[name];
|
|
793
|
-
}
|
|
794
|
-
/** Removes a response header (chainable). */
|
|
795
|
-
removeHeader(name) {
|
|
796
|
-
delete this._headers[name];
|
|
797
|
-
return this;
|
|
798
|
-
}
|
|
799
|
-
/** Returns a read-only snapshot of all response headers. */
|
|
800
|
-
headers() {
|
|
801
|
-
return this._headers;
|
|
802
|
-
}
|
|
803
|
-
/** Sets the `Content-Type` response header (chainable). */
|
|
804
|
-
setContentType(value) {
|
|
805
|
-
this._headers["content-type"] = value;
|
|
806
|
-
return this;
|
|
807
|
-
}
|
|
808
|
-
/** Returns the current `Content-Type` header value. */
|
|
809
|
-
getContentType() {
|
|
810
|
-
return this._headers["content-type"];
|
|
811
|
-
}
|
|
812
|
-
/** Sets the `Access-Control-Allow-Origin` header (chainable). Defaults to `'*'`. */
|
|
813
|
-
enableCors(origin = "*") {
|
|
814
|
-
this._headers["access-control-allow-origin"] = origin;
|
|
815
|
-
return this;
|
|
816
|
-
}
|
|
817
|
-
/** Sets an outgoing `Set-Cookie` header with optional attributes (chainable). */
|
|
818
|
-
setCookie(name, value, attrs) {
|
|
819
|
-
this._cookies[name] = {
|
|
820
|
-
value,
|
|
821
|
-
attrs: attrs || {}
|
|
822
|
-
};
|
|
823
|
-
this._hasCookies = true;
|
|
824
|
-
return this;
|
|
825
|
-
}
|
|
826
|
-
/** Returns a previously set cookie's data, or `undefined` if not set. */
|
|
827
|
-
getCookie(name) {
|
|
828
|
-
return this._cookies[name];
|
|
829
|
-
}
|
|
830
|
-
/** Removes a cookie from the outgoing set list (chainable). */
|
|
831
|
-
removeCookie(name) {
|
|
832
|
-
delete this._cookies[name];
|
|
833
|
-
return this;
|
|
834
|
-
}
|
|
835
|
-
/** Removes all outgoing cookies (chainable). */
|
|
836
|
-
clearCookies() {
|
|
837
|
-
this._cookies = {};
|
|
838
|
-
this._rawCookies = [];
|
|
839
|
-
this._hasCookies = false;
|
|
840
|
-
return this;
|
|
841
|
-
}
|
|
842
|
-
/** Appends a raw `Set-Cookie` header string (chainable). Use when you need full control over the cookie format. */
|
|
843
|
-
setCookieRaw(rawValue) {
|
|
844
|
-
this._rawCookies.push(rawValue);
|
|
845
|
-
this._hasCookies = true;
|
|
846
|
-
return this;
|
|
847
|
-
}
|
|
848
|
-
/** Sets the `Cache-Control` header from a directive object (chainable). */
|
|
849
|
-
setCacheControl(data) {
|
|
850
|
-
this._headers["cache-control"] = renderCacheControl(data);
|
|
851
|
-
return this;
|
|
852
|
-
}
|
|
853
|
-
/** Sets the `Age` header in seconds (chainable). Accepts a number or time string (e.g. `'2h 15m'`). */
|
|
854
|
-
setAge(value) {
|
|
855
|
-
this._headers.age = convertTime(value, "s").toString();
|
|
856
|
-
return this;
|
|
857
|
-
}
|
|
858
|
-
/** Sets the `Expires` header (chainable). Accepts a `Date`, date string, or timestamp. */
|
|
859
|
-
setExpires(value) {
|
|
860
|
-
this._headers.expires = typeof value === "string" || typeof value === "number" ? new Date(value).toUTCString() : value.toUTCString();
|
|
861
|
-
return this;
|
|
862
|
-
}
|
|
863
|
-
/** Sets or clears the `Pragma: no-cache` header (chainable). */
|
|
864
|
-
setPragmaNoCache(value = true) {
|
|
865
|
-
this._headers.pragma = value ? "no-cache" : "";
|
|
866
|
-
return this;
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Returns the underlying Node.js `ServerResponse`.
|
|
870
|
-
* @param passthrough - If `true`, the framework still manages the response lifecycle. If `false` (default), the response is marked as "responded" and the framework will not touch it.
|
|
871
|
-
*/
|
|
872
|
-
getRawRes(passthrough) {
|
|
873
|
-
if (!passthrough) this._responded = true;
|
|
874
|
-
return this._res;
|
|
875
|
-
}
|
|
876
|
-
/** Whether the response has already been sent (or the underlying stream is no longer writable). */
|
|
877
|
-
get responded() {
|
|
878
|
-
return this._responded || !this._res.writable || this._res.writableEnded;
|
|
879
|
-
}
|
|
880
|
-
renderBody() {
|
|
881
|
-
const body = this._body;
|
|
882
|
-
if (body === void 0 || body === null) return "";
|
|
883
|
-
if (typeof body === "string") {
|
|
884
|
-
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
|
|
885
|
-
return body;
|
|
886
|
-
}
|
|
887
|
-
if (typeof body === "boolean" || typeof body === "number") {
|
|
888
|
-
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
|
|
889
|
-
return body.toString();
|
|
890
|
-
}
|
|
891
|
-
if (body instanceof Uint8Array) return body;
|
|
892
|
-
if (typeof body === "object") {
|
|
893
|
-
if (!this._headers["content-type"]) this._headers["content-type"] = "application/json";
|
|
894
|
-
return JSON.stringify(body);
|
|
895
|
-
}
|
|
896
|
-
throw new Error(`Unsupported body format "${typeof body}"`);
|
|
897
|
-
}
|
|
898
|
-
renderError(data, _ctx) {
|
|
899
|
-
this._status = data.statusCode || 500;
|
|
900
|
-
this._headers["content-type"] = "application/json";
|
|
901
|
-
this._body = JSON.stringify(data);
|
|
902
|
-
}
|
|
903
|
-
/** Renders and sends an HTTP error response. Called automatically by the framework when a handler throws an `HttpError`. */
|
|
904
|
-
sendError(error, ctx) {
|
|
905
|
-
const data = error.body;
|
|
906
|
-
this.renderError(data, ctx);
|
|
907
|
-
return this.send();
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Finalizes and sends the response.
|
|
911
|
-
*
|
|
912
|
-
* Flushes all accumulated headers (including cookies) in a single `writeHead()` call,
|
|
913
|
-
* then writes the body. Supports `Readable` streams, `fetch` `Response` objects, and regular values.
|
|
914
|
-
*
|
|
915
|
-
* @throws Error if the response was already sent.
|
|
916
|
-
*/
|
|
917
|
-
send() {
|
|
918
|
-
if (this._responded) {
|
|
919
|
-
const err = /* @__PURE__ */ new Error("The response was already sent.");
|
|
920
|
-
this._logger.error(err.message, err);
|
|
921
|
-
throw err;
|
|
922
|
-
}
|
|
923
|
-
this._responded = true;
|
|
924
|
-
this.finalizeCookies();
|
|
925
|
-
const body = this._body;
|
|
926
|
-
const method = this._req.method;
|
|
927
|
-
if (body instanceof Readable$1) return this.sendStream(body, method);
|
|
928
|
-
if (hasFetchResponse && body instanceof Response) return this.sendFetchResponse(body, method);
|
|
929
|
-
this.sendRegular(method);
|
|
930
|
-
}
|
|
931
|
-
finalizeCookies() {
|
|
932
|
-
if (!this._hasCookies) return;
|
|
933
|
-
const entries = Object.entries(this._cookies);
|
|
934
|
-
const rendered = [];
|
|
935
|
-
for (const [name, data] of entries) if (data) rendered.push(renderCookie(name, data));
|
|
936
|
-
if (this._rawCookies.length > 0) rendered.push(...this._rawCookies);
|
|
937
|
-
if (rendered.length > 0) {
|
|
938
|
-
const existing = this._headers["set-cookie"];
|
|
939
|
-
if (existing) this._headers["set-cookie"] = [...Array.isArray(existing) ? existing : [existing], ...rendered];
|
|
940
|
-
else this._headers["set-cookie"] = rendered;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
autoStatus(hasBody) {
|
|
944
|
-
if (this._status) return;
|
|
945
|
-
if (!hasBody) {
|
|
946
|
-
this._status = EHttpStatusCode.NoContent;
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
this._status = defaultStatus[this._req.method] || EHttpStatusCode.OK;
|
|
950
|
-
}
|
|
951
|
-
sendStream(stream, method) {
|
|
952
|
-
this.autoStatus(true);
|
|
953
|
-
this._res.writeHead(this._status, this._headers);
|
|
954
|
-
this._req.once("close", () => {
|
|
955
|
-
stream.destroy();
|
|
956
|
-
});
|
|
957
|
-
if (method === "HEAD") {
|
|
958
|
-
stream.destroy();
|
|
959
|
-
this._res.end();
|
|
960
|
-
return Promise.resolve();
|
|
961
|
-
}
|
|
962
|
-
return new Promise((resolve, reject) => {
|
|
963
|
-
stream.on("error", (e) => {
|
|
964
|
-
this._logger.error("Stream error", e);
|
|
965
|
-
stream.destroy();
|
|
966
|
-
this._res.end();
|
|
967
|
-
reject(e);
|
|
968
|
-
});
|
|
969
|
-
stream.on("close", () => {
|
|
970
|
-
stream.destroy();
|
|
971
|
-
resolve();
|
|
972
|
-
});
|
|
973
|
-
stream.pipe(this._res);
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
async sendFetchResponse(fetchResponse, method) {
|
|
977
|
-
this._status = this._status || fetchResponse.status;
|
|
978
|
-
const fetchContentLength = fetchResponse.headers.get("content-length");
|
|
979
|
-
if (fetchContentLength) this._headers["content-length"] = fetchContentLength;
|
|
980
|
-
const fetchContentType = fetchResponse.headers.get("content-type");
|
|
981
|
-
if (fetchContentType) this._headers["content-type"] = fetchContentType;
|
|
982
|
-
this._res.writeHead(this._status, this._headers);
|
|
983
|
-
if (method === "HEAD") {
|
|
984
|
-
this._res.end();
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
const fetchBody = fetchResponse.body;
|
|
988
|
-
if (fetchBody) try {
|
|
989
|
-
for await (const chunk of fetchBody) this._res.write(chunk);
|
|
990
|
-
} catch (error) {
|
|
991
|
-
this._logger.error("Error streaming fetch response body", error);
|
|
992
|
-
}
|
|
993
|
-
if (!this._res.writableEnded) this._res.end();
|
|
994
|
-
}
|
|
995
|
-
sendRegular(method) {
|
|
996
|
-
const renderedBody = this.renderBody();
|
|
997
|
-
this.autoStatus(!!renderedBody);
|
|
998
|
-
const contentLength = typeof renderedBody === "string" ? Buffer.byteLength(renderedBody) : renderedBody.byteLength;
|
|
999
|
-
this._headers["content-length"] = contentLength.toString();
|
|
1000
|
-
this._res.writeHead(this._status, this._headers).end(method === "HEAD" ? "" : renderedBody);
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
|
|
1004
|
-
//#endregion
|
|
1005
|
-
//#region packages/event-http/src/event-http.ts
|
|
1006
|
-
/** Creates an async event context for an incoming HTTP request/response pair. */
|
|
1007
|
-
function createHttpContext(data, options, ResponseClass = HttpResponse) {
|
|
1008
|
-
const ctx = new EventContext(options);
|
|
1009
|
-
const response = new ResponseClass(data.res, data.req, ctx.logger);
|
|
1010
|
-
return (fn) => run(ctx, () => ctx.seed(httpKind, {
|
|
1011
|
-
req: data.req,
|
|
1012
|
-
response,
|
|
1013
|
-
requestLimits: data.requestLimits
|
|
1014
|
-
}, fn));
|
|
1015
|
-
}
|
|
1016
|
-
/** Returns the current HTTP event context. */
|
|
1017
|
-
function useHttpContext(ctx) {
|
|
1018
|
-
return ctx ?? current();
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
//#endregion
|
|
1022
|
-
//#region packages/event-http/src/errors/403.tl.svg
|
|
1023
|
-
function _403_tl_default(ctx) {
|
|
1024
|
-
return `<svg height="64" viewBox="0 4 100 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#888888" stroke-width="2">
|
|
1025
|
-
<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">
|
|
1026
|
-
<animate attributeName="fill" dur="2s" repeatCount="indefinite"
|
|
1027
|
-
values="#ff000000;#ff000050;#ff000000" />
|
|
1028
|
-
</path>
|
|
1029
|
-
|
|
1030
|
-
<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" />
|
|
1031
|
-
|
|
1032
|
-
<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" />
|
|
1033
|
-
</svg>
|
|
1034
|
-
`;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
//#endregion
|
|
1038
|
-
//#region packages/event-http/src/errors/404.tl.svg
|
|
1039
|
-
function _404_tl_default(ctx) {
|
|
1040
|
-
return `<svg height="64" viewBox="0 20 100 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1041
|
-
<defs>
|
|
1042
|
-
<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"/>
|
|
1043
|
-
</defs>
|
|
1044
|
-
|
|
1045
|
-
<g id="queue" transform="translate(-5 -10)">
|
|
1046
|
-
<use href="#sheet" opacity="0">
|
|
1047
|
-
<animateTransform attributeName="transform" type="translate"
|
|
1048
|
-
dur="3s" repeatCount="indefinite"
|
|
1049
|
-
keyTimes="0;0.1;0.32;0.42;1"
|
|
1050
|
-
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
1051
|
-
<animate attributeName="opacity"
|
|
1052
|
-
dur="3s" repeatCount="indefinite"
|
|
1053
|
-
keyTimes="0;0.1;0.32;0.42;1"
|
|
1054
|
-
values="0;1;1;0;0" />
|
|
1055
|
-
</use>
|
|
1056
|
-
|
|
1057
|
-
<use href="#sheet" opacity="0">
|
|
1058
|
-
<animateTransform attributeName="transform" type="translate"
|
|
1059
|
-
dur="3s" begin="1s" repeatCount="indefinite"
|
|
1060
|
-
keyTimes="0;0.1;0.32;0.42;1"
|
|
1061
|
-
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
1062
|
-
<animate attributeName="opacity"
|
|
1063
|
-
dur="3s" begin="1s" repeatCount="indefinite"
|
|
1064
|
-
keyTimes="0;0.1;0.32;0.42;1"
|
|
1065
|
-
values="0;1;1;0;0" />
|
|
1066
|
-
</use>
|
|
1067
|
-
|
|
1068
|
-
<use href="#sheet" opacity="0">
|
|
1069
|
-
<animateTransform attributeName="transform" type="translate"
|
|
1070
|
-
dur="3s" begin="2s" repeatCount="indefinite"
|
|
1071
|
-
keyTimes="0;0.1;0.32;0.42;1"
|
|
1072
|
-
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
1073
|
-
<animate attributeName="opacity"
|
|
1074
|
-
dur="3s" begin="2s" repeatCount="indefinite"
|
|
1075
|
-
keyTimes="0;0.1;0.32;0.42;1"
|
|
1076
|
-
values="0;1;1;0;0" />
|
|
1077
|
-
</use>
|
|
1078
|
-
</g>
|
|
1079
|
-
|
|
1080
|
-
<g>
|
|
1081
|
-
<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"/>
|
|
1082
|
-
|
|
1083
|
-
<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"/>
|
|
1084
|
-
</g>
|
|
1085
|
-
</svg>
|
|
1086
|
-
|
|
1087
|
-
`;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
//#endregion
|
|
1091
|
-
//#region packages/event-http/src/errors/500.tl.svg
|
|
1092
|
-
function _500_tl_default(ctx) {
|
|
1093
|
-
return `<svg height="64" viewBox="0 0 120 100" xmlns="http://www.w3.org/2000/svg">
|
|
1094
|
-
|
|
1095
|
-
<g id="server">
|
|
1096
|
-
|
|
1097
|
-
<g fill="#88888888" stroke="#88888888" stroke-width="2" >
|
|
1098
|
-
<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" />
|
|
1099
|
-
</g>
|
|
1100
|
-
<circle cx="30" cy="60" r="6" fill="red">
|
|
1101
|
-
<animate attributeName="fill" dur="0.8s"
|
|
1102
|
-
values="red;#2d0000;red" repeatCount="indefinite"/>
|
|
1103
|
-
</circle>
|
|
1104
|
-
|
|
1105
|
-
</g>
|
|
1106
|
-
|
|
1107
|
-
<g fill="lightgray" opacity="0.75">
|
|
1108
|
-
<circle cx="50" cy="35" r="6">
|
|
1109
|
-
<animate attributeName="cy" from="35" to="15" dur="2s"
|
|
1110
|
-
repeatCount="indefinite"/>
|
|
1111
|
-
<animate attributeName="opacity" values="0.75;0" dur="2s"
|
|
1112
|
-
repeatCount="indefinite"/>
|
|
1113
|
-
</circle>
|
|
1114
|
-
<circle cx="60" cy="40" r="4">
|
|
1115
|
-
<animate attributeName="cy" from="40" to="20" dur="2s"
|
|
1116
|
-
begin="0.4s" repeatCount="indefinite"/>
|
|
1117
|
-
<animate attributeName="opacity" values="0.75;0" dur="2s"
|
|
1118
|
-
begin="0.4s" repeatCount="indefinite"/>
|
|
1119
|
-
</circle>
|
|
1120
|
-
</g>
|
|
1121
|
-
|
|
1122
|
-
</svg>
|
|
1123
|
-
`;
|
|
716
|
+
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region packages/event-http/src/errors/500.tl.svg
|
|
719
|
+
function _500_tl_default(ctx) {
|
|
720
|
+
return `<svg height="64" viewBox="0 0 120 100" xmlns="http://www.w3.org/2000/svg">
|
|
721
|
+
|
|
722
|
+
<g id="server">
|
|
723
|
+
|
|
724
|
+
<g fill="#88888888" stroke="#88888888" stroke-width="2" >
|
|
725
|
+
<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" />
|
|
726
|
+
</g>
|
|
727
|
+
<circle cx="30" cy="60" r="6" fill="red">
|
|
728
|
+
<animate attributeName="fill" dur="0.8s"
|
|
729
|
+
values="red;#2d0000;red" repeatCount="indefinite"/>
|
|
730
|
+
</circle>
|
|
731
|
+
|
|
732
|
+
</g>
|
|
733
|
+
|
|
734
|
+
<g fill="lightgray" opacity="0.75">
|
|
735
|
+
<circle cx="50" cy="35" r="6">
|
|
736
|
+
<animate attributeName="cy" from="35" to="15" dur="2s"
|
|
737
|
+
repeatCount="indefinite"/>
|
|
738
|
+
<animate attributeName="opacity" values="0.75;0" dur="2s"
|
|
739
|
+
repeatCount="indefinite"/>
|
|
740
|
+
</circle>
|
|
741
|
+
<circle cx="60" cy="40" r="4">
|
|
742
|
+
<animate attributeName="cy" from="40" to="20" dur="2s"
|
|
743
|
+
begin="0.4s" repeatCount="indefinite"/>
|
|
744
|
+
<animate attributeName="opacity" values="0.75;0" dur="2s"
|
|
745
|
+
begin="0.4s" repeatCount="indefinite"/>
|
|
746
|
+
</circle>
|
|
747
|
+
</g>
|
|
748
|
+
|
|
749
|
+
</svg>
|
|
750
|
+
`;
|
|
1124
751
|
}
|
|
1125
752
|
|
|
1126
753
|
//#endregion
|
|
@@ -1368,13 +995,380 @@ function error_tl_default(ctx) {
|
|
|
1368
995
|
`;
|
|
1369
996
|
}
|
|
1370
997
|
|
|
998
|
+
//#endregion
|
|
999
|
+
//#region packages/event-http/src/utils/time.ts
|
|
1000
|
+
function convertTime(time, unit = "ms") {
|
|
1001
|
+
if (typeof time === "number") return time / units[unit];
|
|
1002
|
+
const rg = /(\d+)(\w+)/gu;
|
|
1003
|
+
let t = 0;
|
|
1004
|
+
let r;
|
|
1005
|
+
while (r = rg.exec(time)) t += Number(r[1]) * (units[r[2]] || 0);
|
|
1006
|
+
return t / units[unit];
|
|
1007
|
+
}
|
|
1008
|
+
const units = {
|
|
1009
|
+
ms: 1,
|
|
1010
|
+
s: 1e3,
|
|
1011
|
+
m: 1e3 * 60,
|
|
1012
|
+
h: 1e3 * 60 * 60,
|
|
1013
|
+
d: 1e3 * 60 * 60 * 24,
|
|
1014
|
+
w: 1e3 * 60 * 60 * 24 * 7,
|
|
1015
|
+
M: 1e3 * 60 * 60 * 24 * 30,
|
|
1016
|
+
Y: 1e3 * 60 * 60 * 24 * 365
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
//#endregion
|
|
1020
|
+
//#region packages/event-http/src/utils/cache-control.ts
|
|
1021
|
+
/** Renders a `TCacheControl` object into a `Cache-Control` header string. */
|
|
1022
|
+
function renderCacheControl(data) {
|
|
1023
|
+
let attrs = "";
|
|
1024
|
+
for (const [a, v] of Object.entries(data)) {
|
|
1025
|
+
if (v === void 0) continue;
|
|
1026
|
+
const func = cacheControlFunc[a];
|
|
1027
|
+
if (typeof func === "function") {
|
|
1028
|
+
const val = func(v);
|
|
1029
|
+
if (val) attrs += attrs ? `, ${val}` : val;
|
|
1030
|
+
} else throw new TypeError(`Unknown Cache-Control attribute ${a}`);
|
|
1031
|
+
}
|
|
1032
|
+
return attrs;
|
|
1033
|
+
}
|
|
1034
|
+
const cacheControlFunc = {
|
|
1035
|
+
mustRevalidate: (v) => v ? "must-revalidate" : "",
|
|
1036
|
+
noCache: (v) => v ? typeof v === "string" ? `no-cache="${v}"` : "no-cache" : "",
|
|
1037
|
+
noStore: (v) => v ? "no-store" : "",
|
|
1038
|
+
noTransform: (v) => v ? "no-transform" : "",
|
|
1039
|
+
public: (v) => v ? "public" : "",
|
|
1040
|
+
private: (v) => v ? typeof v === "string" ? `private="${v}"` : "private" : "",
|
|
1041
|
+
proxyRevalidate: (v) => v ? "proxy-revalidate" : "",
|
|
1042
|
+
maxAge: (v) => `max-age=${convertTime(v, "s").toString()}`,
|
|
1043
|
+
sMaxage: (v) => `s-maxage=${convertTime(v, "s").toString()}`
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
//#endregion
|
|
1047
|
+
//#region packages/event-http/src/utils/set-cookie.ts
|
|
1048
|
+
const COOKIE_NAME_RE = /^[\w!#$%&'*+\-.^`|~]+$/;
|
|
1049
|
+
function sanitizeCookieAttrValue(v) {
|
|
1050
|
+
return v.replace(/[;\r\n]/g, "");
|
|
1051
|
+
}
|
|
1052
|
+
function renderCookie(key, data) {
|
|
1053
|
+
if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
|
|
1054
|
+
let attrs = "";
|
|
1055
|
+
for (const [a, v] of Object.entries(data.attrs)) {
|
|
1056
|
+
const func = cookieAttrFunc[a];
|
|
1057
|
+
if (typeof func === "function") {
|
|
1058
|
+
const val = func(v);
|
|
1059
|
+
attrs += val ? `; ${val}` : "";
|
|
1060
|
+
} else throw new TypeError(`Unknown Set-Cookie attribute ${a}`);
|
|
1061
|
+
}
|
|
1062
|
+
return `${key}=${encodeURIComponent(data.value)}${attrs}`;
|
|
1063
|
+
}
|
|
1064
|
+
const cookieAttrFunc = {
|
|
1065
|
+
expires: (v) => `Expires=${typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString()}`,
|
|
1066
|
+
maxAge: (v) => `Max-Age=${convertTime(v, "s").toString()}`,
|
|
1067
|
+
domain: (v) => `Domain=${sanitizeCookieAttrValue(String(v))}`,
|
|
1068
|
+
path: (v) => `Path=${sanitizeCookieAttrValue(String(v))}`,
|
|
1069
|
+
secure: (v) => v ? "Secure" : "",
|
|
1070
|
+
httpOnly: (v) => v ? "HttpOnly" : "",
|
|
1071
|
+
sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
//#endregion
|
|
1075
|
+
//#region packages/event-http/src/response/http-response.ts
|
|
1076
|
+
const hasFetchResponse = typeof globalThis.Response === "function";
|
|
1077
|
+
const defaultStatus = {
|
|
1078
|
+
GET: EHttpStatusCode.OK,
|
|
1079
|
+
POST: EHttpStatusCode.Created,
|
|
1080
|
+
PUT: EHttpStatusCode.Created,
|
|
1081
|
+
PATCH: EHttpStatusCode.Accepted,
|
|
1082
|
+
DELETE: EHttpStatusCode.Accepted
|
|
1083
|
+
};
|
|
1084
|
+
/**
|
|
1085
|
+
* Manages response status, headers, cookies, cache control, and body for an HTTP request.
|
|
1086
|
+
*
|
|
1087
|
+
* All header mutations are accumulated in memory and flushed in a single `writeHead()` call
|
|
1088
|
+
* when `send()` is invoked. Setter methods are chainable.
|
|
1089
|
+
*
|
|
1090
|
+
* @example
|
|
1091
|
+
* ```ts
|
|
1092
|
+
* const response = useResponse()
|
|
1093
|
+
* response.setStatus(200).setHeader('x-custom', 'value')
|
|
1094
|
+
* response.setCookie('session', 'abc', { httpOnly: true })
|
|
1095
|
+
* ```
|
|
1096
|
+
*/
|
|
1097
|
+
var HttpResponse = class {
|
|
1098
|
+
/**
|
|
1099
|
+
* @param _res - The underlying Node.js `ServerResponse`.
|
|
1100
|
+
* @param _req - The underlying Node.js `IncomingMessage`.
|
|
1101
|
+
* @param _logger - Logger instance for error reporting.
|
|
1102
|
+
* @param defaultHeaders - Optional headers to pre-populate on this response (e.g. from `securityHeaders()`).
|
|
1103
|
+
*/
|
|
1104
|
+
constructor(_res, _req, _logger, defaultHeaders) {
|
|
1105
|
+
this._res = _res;
|
|
1106
|
+
this._req = _req;
|
|
1107
|
+
this._logger = _logger;
|
|
1108
|
+
if (defaultHeaders) for (const key in defaultHeaders) this._headers[key] = defaultHeaders[key];
|
|
1109
|
+
}
|
|
1110
|
+
_status = 0;
|
|
1111
|
+
_body = void 0;
|
|
1112
|
+
_headers = {};
|
|
1113
|
+
_cookies = {};
|
|
1114
|
+
_rawCookies = [];
|
|
1115
|
+
_hasCookies = false;
|
|
1116
|
+
_responded = false;
|
|
1117
|
+
/** The HTTP status code. If not set, it is inferred automatically when `send()` is called. */
|
|
1118
|
+
get status() {
|
|
1119
|
+
return this._status;
|
|
1120
|
+
}
|
|
1121
|
+
set status(value) {
|
|
1122
|
+
this._status = value;
|
|
1123
|
+
}
|
|
1124
|
+
/** Sets the HTTP status code (chainable). */
|
|
1125
|
+
setStatus(value) {
|
|
1126
|
+
this._status = value;
|
|
1127
|
+
return this;
|
|
1128
|
+
}
|
|
1129
|
+
/** The response body. Automatically serialized by `send()` (objects → JSON, strings → text). */
|
|
1130
|
+
get body() {
|
|
1131
|
+
return this._body;
|
|
1132
|
+
}
|
|
1133
|
+
set body(value) {
|
|
1134
|
+
this._body = value;
|
|
1135
|
+
}
|
|
1136
|
+
/** Sets the response body (chainable). */
|
|
1137
|
+
setBody(value) {
|
|
1138
|
+
this._body = value;
|
|
1139
|
+
return this;
|
|
1140
|
+
}
|
|
1141
|
+
/** Sets a single response header (chainable). Arrays produce multi-value headers. */
|
|
1142
|
+
setHeader(name, value) {
|
|
1143
|
+
this._headers[name] = Array.isArray(value) ? value : value.toString();
|
|
1144
|
+
return this;
|
|
1145
|
+
}
|
|
1146
|
+
/** Batch-sets multiple response headers from a record (chainable). Existing keys are overwritten. */
|
|
1147
|
+
setHeaders(headers) {
|
|
1148
|
+
for (const key in headers) this._headers[key] = headers[key];
|
|
1149
|
+
return this;
|
|
1150
|
+
}
|
|
1151
|
+
/** Returns the value of a response header, or `undefined` if not set. */
|
|
1152
|
+
getHeader(name) {
|
|
1153
|
+
return this._headers[name];
|
|
1154
|
+
}
|
|
1155
|
+
/** Removes a response header (chainable). */
|
|
1156
|
+
removeHeader(name) {
|
|
1157
|
+
delete this._headers[name];
|
|
1158
|
+
return this;
|
|
1159
|
+
}
|
|
1160
|
+
/** Returns a read-only snapshot of all response headers. */
|
|
1161
|
+
headers() {
|
|
1162
|
+
return this._headers;
|
|
1163
|
+
}
|
|
1164
|
+
/** Sets the `Content-Type` response header (chainable). */
|
|
1165
|
+
setContentType(value) {
|
|
1166
|
+
this._headers["content-type"] = value;
|
|
1167
|
+
return this;
|
|
1168
|
+
}
|
|
1169
|
+
/** Returns the current `Content-Type` header value. */
|
|
1170
|
+
getContentType() {
|
|
1171
|
+
return this._headers["content-type"];
|
|
1172
|
+
}
|
|
1173
|
+
/** Sets the `Access-Control-Allow-Origin` header (chainable). Defaults to `'*'`. */
|
|
1174
|
+
enableCors(origin = "*") {
|
|
1175
|
+
this._headers["access-control-allow-origin"] = origin;
|
|
1176
|
+
return this;
|
|
1177
|
+
}
|
|
1178
|
+
/** Sets an outgoing `Set-Cookie` header with optional attributes (chainable). */
|
|
1179
|
+
setCookie(name, value, attrs) {
|
|
1180
|
+
this._cookies[name] = {
|
|
1181
|
+
value,
|
|
1182
|
+
attrs: attrs || {}
|
|
1183
|
+
};
|
|
1184
|
+
this._hasCookies = true;
|
|
1185
|
+
return this;
|
|
1186
|
+
}
|
|
1187
|
+
/** Returns a previously set cookie's data, or `undefined` if not set. */
|
|
1188
|
+
getCookie(name) {
|
|
1189
|
+
return this._cookies[name];
|
|
1190
|
+
}
|
|
1191
|
+
/** Removes a cookie from the outgoing set list (chainable). */
|
|
1192
|
+
removeCookie(name) {
|
|
1193
|
+
delete this._cookies[name];
|
|
1194
|
+
return this;
|
|
1195
|
+
}
|
|
1196
|
+
/** Removes all outgoing cookies (chainable). */
|
|
1197
|
+
clearCookies() {
|
|
1198
|
+
this._cookies = {};
|
|
1199
|
+
this._rawCookies = [];
|
|
1200
|
+
this._hasCookies = false;
|
|
1201
|
+
return this;
|
|
1202
|
+
}
|
|
1203
|
+
/** Appends a raw `Set-Cookie` header string (chainable). Use when you need full control over the cookie format. */
|
|
1204
|
+
setCookieRaw(rawValue) {
|
|
1205
|
+
this._rawCookies.push(rawValue);
|
|
1206
|
+
this._hasCookies = true;
|
|
1207
|
+
return this;
|
|
1208
|
+
}
|
|
1209
|
+
/** Sets the `Cache-Control` header from a directive object (chainable). */
|
|
1210
|
+
setCacheControl(data) {
|
|
1211
|
+
this._headers["cache-control"] = renderCacheControl(data);
|
|
1212
|
+
return this;
|
|
1213
|
+
}
|
|
1214
|
+
/** Sets the `Age` header in seconds (chainable). Accepts a number or time string (e.g. `'2h 15m'`). */
|
|
1215
|
+
setAge(value) {
|
|
1216
|
+
this._headers.age = convertTime(value, "s").toString();
|
|
1217
|
+
return this;
|
|
1218
|
+
}
|
|
1219
|
+
/** Sets the `Expires` header (chainable). Accepts a `Date`, date string, or timestamp. */
|
|
1220
|
+
setExpires(value) {
|
|
1221
|
+
this._headers.expires = typeof value === "string" || typeof value === "number" ? new Date(value).toUTCString() : value.toUTCString();
|
|
1222
|
+
return this;
|
|
1223
|
+
}
|
|
1224
|
+
/** Sets or clears the `Pragma: no-cache` header (chainable). */
|
|
1225
|
+
setPragmaNoCache(value = true) {
|
|
1226
|
+
this._headers.pragma = value ? "no-cache" : "";
|
|
1227
|
+
return this;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Returns the underlying Node.js `ServerResponse`.
|
|
1231
|
+
* @param passthrough - If `true`, the framework still manages the response lifecycle. If `false` (default), the response is marked as "responded" and the framework will not touch it.
|
|
1232
|
+
*/
|
|
1233
|
+
getRawRes(passthrough) {
|
|
1234
|
+
if (!passthrough) this._responded = true;
|
|
1235
|
+
return this._res;
|
|
1236
|
+
}
|
|
1237
|
+
/** Whether the response has already been sent (or the underlying stream is no longer writable). */
|
|
1238
|
+
get responded() {
|
|
1239
|
+
return this._responded || !this._res.writable || this._res.writableEnded;
|
|
1240
|
+
}
|
|
1241
|
+
renderBody() {
|
|
1242
|
+
const body = this._body;
|
|
1243
|
+
if (body === void 0 || body === null) return "";
|
|
1244
|
+
if (typeof body === "string") {
|
|
1245
|
+
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
|
|
1246
|
+
return body;
|
|
1247
|
+
}
|
|
1248
|
+
if (typeof body === "boolean" || typeof body === "number") {
|
|
1249
|
+
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
|
|
1250
|
+
return body.toString();
|
|
1251
|
+
}
|
|
1252
|
+
if (body instanceof Uint8Array) return body;
|
|
1253
|
+
if (typeof body === "object") {
|
|
1254
|
+
if (!this._headers["content-type"]) this._headers["content-type"] = "application/json";
|
|
1255
|
+
return JSON.stringify(body);
|
|
1256
|
+
}
|
|
1257
|
+
throw new Error(`Unsupported body format "${typeof body}"`);
|
|
1258
|
+
}
|
|
1259
|
+
renderError(data, _ctx) {
|
|
1260
|
+
this._status = data.statusCode || 500;
|
|
1261
|
+
this._headers["content-type"] = "application/json";
|
|
1262
|
+
this._body = JSON.stringify(data);
|
|
1263
|
+
}
|
|
1264
|
+
/** Renders and sends an HTTP error response. Called automatically by the framework when a handler throws an `HttpError`. */
|
|
1265
|
+
sendError(error, ctx) {
|
|
1266
|
+
const data = error.body;
|
|
1267
|
+
this.renderError(data, ctx);
|
|
1268
|
+
return this.send();
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Finalizes and sends the response.
|
|
1272
|
+
*
|
|
1273
|
+
* Flushes all accumulated headers (including cookies) in a single `writeHead()` call,
|
|
1274
|
+
* then writes the body. Supports `Readable` streams, `fetch` `Response` objects, and regular values.
|
|
1275
|
+
*
|
|
1276
|
+
* @throws Error if the response was already sent.
|
|
1277
|
+
*/
|
|
1278
|
+
send() {
|
|
1279
|
+
if (this._responded) {
|
|
1280
|
+
const err = /* @__PURE__ */ new Error("The response was already sent.");
|
|
1281
|
+
this._logger.error(err.message, err);
|
|
1282
|
+
throw err;
|
|
1283
|
+
}
|
|
1284
|
+
this._responded = true;
|
|
1285
|
+
this.finalizeCookies();
|
|
1286
|
+
const body = this._body;
|
|
1287
|
+
const method = this._req.method;
|
|
1288
|
+
if (body instanceof Readable$1) return this.sendStream(body, method);
|
|
1289
|
+
if (hasFetchResponse && body instanceof Response) return this.sendFetchResponse(body, method);
|
|
1290
|
+
this.sendRegular(method);
|
|
1291
|
+
}
|
|
1292
|
+
finalizeCookies() {
|
|
1293
|
+
if (!this._hasCookies) return;
|
|
1294
|
+
const entries = Object.entries(this._cookies);
|
|
1295
|
+
const rendered = [];
|
|
1296
|
+
for (const [name, data] of entries) if (data) rendered.push(renderCookie(name, data));
|
|
1297
|
+
if (this._rawCookies.length > 0) rendered.push(...this._rawCookies);
|
|
1298
|
+
if (rendered.length > 0) {
|
|
1299
|
+
const existing = this._headers["set-cookie"];
|
|
1300
|
+
if (existing) this._headers["set-cookie"] = [...Array.isArray(existing) ? existing : [existing], ...rendered];
|
|
1301
|
+
else this._headers["set-cookie"] = rendered;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
autoStatus(hasBody) {
|
|
1305
|
+
if (this._status) return;
|
|
1306
|
+
if (!hasBody) {
|
|
1307
|
+
this._status = EHttpStatusCode.NoContent;
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
this._status = defaultStatus[this._req.method] || EHttpStatusCode.OK;
|
|
1311
|
+
}
|
|
1312
|
+
sendStream(stream, method) {
|
|
1313
|
+
this.autoStatus(true);
|
|
1314
|
+
this._res.writeHead(this._status, this._headers);
|
|
1315
|
+
this._req.once("close", () => {
|
|
1316
|
+
stream.destroy();
|
|
1317
|
+
});
|
|
1318
|
+
if (method === "HEAD") {
|
|
1319
|
+
stream.destroy();
|
|
1320
|
+
this._res.end();
|
|
1321
|
+
return Promise.resolve();
|
|
1322
|
+
}
|
|
1323
|
+
return new Promise((resolve, reject) => {
|
|
1324
|
+
stream.on("error", (e) => {
|
|
1325
|
+
this._logger.error("Stream error", e);
|
|
1326
|
+
stream.destroy();
|
|
1327
|
+
this._res.end();
|
|
1328
|
+
reject(e);
|
|
1329
|
+
});
|
|
1330
|
+
stream.on("close", () => {
|
|
1331
|
+
stream.destroy();
|
|
1332
|
+
resolve();
|
|
1333
|
+
});
|
|
1334
|
+
stream.pipe(this._res);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
async sendFetchResponse(fetchResponse, method) {
|
|
1338
|
+
this._status = this._status || fetchResponse.status;
|
|
1339
|
+
const fetchContentLength = fetchResponse.headers.get("content-length");
|
|
1340
|
+
if (fetchContentLength) this._headers["content-length"] = fetchContentLength;
|
|
1341
|
+
const fetchContentType = fetchResponse.headers.get("content-type");
|
|
1342
|
+
if (fetchContentType) this._headers["content-type"] = fetchContentType;
|
|
1343
|
+
this._res.writeHead(this._status, this._headers);
|
|
1344
|
+
if (method === "HEAD") {
|
|
1345
|
+
this._res.end();
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const fetchBody = fetchResponse.body;
|
|
1349
|
+
if (fetchBody) try {
|
|
1350
|
+
for await (const chunk of fetchBody) this._res.write(chunk);
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
this._logger.error("Error streaming fetch response body", error);
|
|
1353
|
+
}
|
|
1354
|
+
if (!this._res.writableEnded) this._res.end();
|
|
1355
|
+
}
|
|
1356
|
+
sendRegular(method) {
|
|
1357
|
+
const renderedBody = this.renderBody();
|
|
1358
|
+
this.autoStatus(!!renderedBody);
|
|
1359
|
+
const contentLength = typeof renderedBody === "string" ? Buffer.byteLength(renderedBody) : renderedBody.byteLength;
|
|
1360
|
+
this._headers["content-length"] = contentLength.toString();
|
|
1361
|
+
this._res.writeHead(this._status, this._headers).end(method === "HEAD" ? "" : renderedBody);
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1371
1365
|
//#endregion
|
|
1372
1366
|
//#region packages/event-http/src/response/wooks-http-response.ts
|
|
1373
1367
|
let framework = {
|
|
1374
|
-
version: "0.7.
|
|
1368
|
+
version: "0.7.2",
|
|
1375
1369
|
poweredBy: "wooksjs",
|
|
1376
1370
|
link: "https://wooks.moost.org/",
|
|
1377
|
-
image: "https://wooks.moost.org/wooks-full-logo.
|
|
1371
|
+
image: "https://wooks.moost.org/wooks-full-logo.svg"
|
|
1378
1372
|
};
|
|
1379
1373
|
const icons = {
|
|
1380
1374
|
401: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
@@ -1578,16 +1572,15 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1578
1572
|
const notFoundHandler = this.opts?.onNotFound;
|
|
1579
1573
|
const defaultHeaders = this.opts?.defaultHeaders;
|
|
1580
1574
|
return (req, res) => {
|
|
1581
|
-
const
|
|
1582
|
-
const response = new this.ResponseClass(res, req, ctx.logger, defaultHeaders);
|
|
1575
|
+
const response = new this.ResponseClass(res, req, ctxOptions.logger, defaultHeaders);
|
|
1583
1576
|
const method = req.method || "";
|
|
1584
1577
|
const url = req.url || "";
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1578
|
+
createHttpContext(ctxOptions, {
|
|
1579
|
+
req,
|
|
1580
|
+
response,
|
|
1581
|
+
requestLimits: RequestLimits
|
|
1582
|
+
}, () => {
|
|
1583
|
+
const ctx = current();
|
|
1591
1584
|
const handlers = this.wooks.lookupHandlers(method, url, ctx);
|
|
1592
1585
|
if (handlers || notFoundHandler) {
|
|
1593
1586
|
const result = this.processHandlers(handlers || [notFoundHandler], ctx, response);
|
|
@@ -1595,10 +1588,12 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1595
1588
|
this.logger.error("Internal error, please report", error);
|
|
1596
1589
|
this.respond(error, response, ctx);
|
|
1597
1590
|
});
|
|
1591
|
+
return result;
|
|
1598
1592
|
} else {
|
|
1599
1593
|
this.logger.debug(`404 Not found (${method})${url}`);
|
|
1600
1594
|
const error = new HttpError(404);
|
|
1601
1595
|
this.respond(error, response, ctx);
|
|
1596
|
+
return error;
|
|
1602
1597
|
}
|
|
1603
1598
|
});
|
|
1604
1599
|
};
|
|
@@ -1616,20 +1611,19 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1616
1611
|
socket.destroy();
|
|
1617
1612
|
return;
|
|
1618
1613
|
}
|
|
1619
|
-
const ctx = new EventContext(ctxOptions);
|
|
1620
1614
|
const url = req.url || "";
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1615
|
+
createHttpContext(ctxOptions, {
|
|
1616
|
+
req,
|
|
1617
|
+
response: void 0,
|
|
1618
|
+
requestLimits
|
|
1619
|
+
}, () => {
|
|
1620
|
+
const ctx = current();
|
|
1627
1621
|
ctx.set(wsHandler.reqKey, req);
|
|
1628
1622
|
ctx.set(wsHandler.socketKey, socket);
|
|
1629
1623
|
ctx.set(wsHandler.headKey, head);
|
|
1630
1624
|
const handlers = this.wooks.lookupHandlers("UPGRADE", url, ctx);
|
|
1631
|
-
if (handlers) this.processUpgradeHandlers(handlers, ctx, socket);
|
|
1632
|
-
else wsHandler.handleUpgrade(req, socket, head);
|
|
1625
|
+
if (handlers) return this.processUpgradeHandlers(handlers, ctx, socket);
|
|
1626
|
+
else return wsHandler.handleUpgrade(req, socket, head);
|
|
1633
1627
|
});
|
|
1634
1628
|
};
|
|
1635
1629
|
}
|