srvx 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,80 @@
1
+ import NodeHttp from 'node:http';
1
2
  import { splitSetCookieString } from 'cookie-es';
3
+ import { r as resolvePort, f as fmtURL } from '../shared/srvx.PbkQy9Ck.mjs';
4
+ import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
5
+
6
+ async function sendNodeResponse(nodeRes, webRes) {
7
+ if (!webRes) {
8
+ nodeRes.statusCode = 500;
9
+ return endNodeResponse(nodeRes);
10
+ }
11
+ if (webRes.nodeResponse) {
12
+ const res = webRes.nodeResponse();
13
+ nodeRes.writeHead(res.status, res.statusText, res.headers);
14
+ if (res.body instanceof ReadableStream) {
15
+ return streamBody(res.body, nodeRes);
16
+ }
17
+ nodeRes.write(res.body);
18
+ return endNodeResponse(nodeRes);
19
+ }
20
+ const headerEntries = [];
21
+ for (const [key, value] of webRes.headers) {
22
+ if (key === "set-cookie") {
23
+ for (const setCookie of splitSetCookieString(value)) {
24
+ headerEntries.push(["set-cookie", setCookie]);
25
+ }
26
+ } else {
27
+ headerEntries.push([key, value]);
28
+ }
29
+ }
30
+ nodeRes.writeHead(webRes.status || 200, webRes.statusText, headerEntries);
31
+ return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
32
+ }
33
+ function endNodeResponse(nodeRes) {
34
+ return new Promise((resolve) => nodeRes.end(resolve));
35
+ }
36
+ function streamBody(stream, nodeRes) {
37
+ if (nodeRes.destroyed) {
38
+ stream.cancel();
39
+ return;
40
+ }
41
+ const reader = stream.getReader();
42
+ function streamCancel(error) {
43
+ reader.cancel(error).catch(() => {
44
+ });
45
+ if (error) {
46
+ nodeRes.destroy(error);
47
+ }
48
+ }
49
+ function streamHandle({
50
+ done,
51
+ value
52
+ }) {
53
+ try {
54
+ if (done) {
55
+ nodeRes.end();
56
+ } else if (nodeRes.write(value)) {
57
+ reader.read().then(streamHandle, streamCancel);
58
+ } else {
59
+ nodeRes.once(
60
+ "drain",
61
+ () => reader.read().then(streamHandle, streamCancel)
62
+ );
63
+ }
64
+ } catch (error) {
65
+ streamCancel(error instanceof Error ? error : void 0);
66
+ }
67
+ }
68
+ nodeRes.on("close", streamCancel);
69
+ nodeRes.on("error", streamCancel);
70
+ reader.read().then(streamHandle, streamCancel);
71
+ return reader.closed.finally(() => {
72
+ nodeRes.off("close", streamCancel);
73
+ nodeRes.off("error", streamCancel);
74
+ });
75
+ }
2
76
 
3
77
  const kNodeReq = /* @__PURE__ */ Symbol.for("srvx.node.request");
4
- const kNodeRes = /* @__PURE__ */ Symbol.for("srvx.node.response");
5
78
  const kNodeInspect = /* @__PURE__ */ Symbol.for(
6
79
  "nodejs.util.inspect.custom"
7
80
  );
@@ -11,6 +84,7 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => class NodeReqHeadersProxy {
11
84
  this[kNodeReq] = req;
12
85
  }
13
86
  append(name, value) {
87
+ name = name.toLowerCase();
14
88
  const _headers = this[kNodeReq].headers;
15
89
  const _current = _headers[name];
16
90
  if (_current) {
@@ -24,9 +98,11 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => class NodeReqHeadersProxy {
24
98
  }
25
99
  }
26
100
  delete(name) {
101
+ name = name.toLowerCase();
27
102
  this[kNodeReq].headers[name] = void 0;
28
103
  }
29
104
  get(name) {
105
+ name = name.toLowerCase();
30
106
  return _normalizeValue(this[kNodeReq].headers[name]);
31
107
  }
32
108
  getSetCookie() {
@@ -37,11 +113,19 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => class NodeReqHeadersProxy {
37
113
  return splitSetCookieString(setCookie);
38
114
  }
39
115
  has(name) {
116
+ name = name.toLowerCase();
40
117
  return !!this[kNodeReq].headers[name];
41
118
  }
42
119
  set(name, value) {
120
+ name = name.toLowerCase();
43
121
  this[kNodeReq].headers[name] = value;
44
122
  }
123
+ get count() {
124
+ throw new Error("Method not implemented.");
125
+ }
126
+ getAll(_name) {
127
+ throw new Error("Method not implemented.");
128
+ }
45
129
  toJSON() {
46
130
  const _headers = this[kNodeReq].headers;
47
131
  const result = {};
@@ -93,73 +177,6 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => class NodeReqHeadersProxy {
93
177
  return Object.fromEntries(this.entries());
94
178
  }
95
179
  })();
96
- const NodeResHeadersProxy = /* @__PURE__ */ (() => class NodeResHeadersProxy {
97
- constructor(res) {
98
- this[kNodeRes] = res;
99
- }
100
- append(name, value) {
101
- this[kNodeRes].appendHeader(name, value);
102
- }
103
- delete(name) {
104
- this[kNodeRes].removeHeader(name);
105
- }
106
- get(name) {
107
- return _normalizeValue(this[kNodeRes].getHeader(name));
108
- }
109
- getSetCookie() {
110
- const setCookie = _normalizeValue(this[kNodeRes].getHeader("set-cookie"));
111
- if (!setCookie) {
112
- return [];
113
- }
114
- return splitSetCookieString(setCookie);
115
- }
116
- has(name) {
117
- return this[kNodeRes].hasHeader(name);
118
- }
119
- set(name, value) {
120
- this[kNodeRes].setHeader(name, value);
121
- }
122
- forEach(cb, thisArg) {
123
- const _headers = this[kNodeRes].getHeaders();
124
- for (const key in _headers) {
125
- if (_headers[key]) {
126
- cb.call(
127
- thisArg,
128
- _normalizeValue(_headers[key]),
129
- key,
130
- this
131
- );
132
- }
133
- }
134
- }
135
- *entries() {
136
- const _headers = this[kNodeRes].getHeaders();
137
- for (const key in _headers) {
138
- yield [key, _normalizeValue(_headers[key])];
139
- }
140
- }
141
- *keys() {
142
- const keys = this[kNodeRes].getHeaderNames();
143
- for (const key of keys) {
144
- yield key;
145
- }
146
- }
147
- *values() {
148
- const values = Object.values(this[kNodeRes].getHeaders());
149
- for (const value of values) {
150
- yield _normalizeValue(value);
151
- }
152
- }
153
- [(Symbol.iterator)]() {
154
- return this.entries()[Symbol.iterator]();
155
- }
156
- get [Symbol.toStringTag]() {
157
- return "Headers";
158
- }
159
- [kNodeInspect]() {
160
- return Object.fromEntries(this.entries());
161
- }
162
- })();
163
180
  function _normalizeValue(value) {
164
181
  if (Array.isArray(value)) {
165
182
  return value.join(", ");
@@ -348,7 +365,7 @@ const NodeRequestProxy = /* @__PURE__ */ (() => class NodeRequestProxy2 {
348
365
  #jsonBody;
349
366
  #textBody;
350
367
  #bodyStream;
351
- get xRemoteAddress() {
368
+ get remoteAddress() {
352
369
  return this[kNodeReq].socket?.remoteAddress;
353
370
  }
354
371
  clone() {
@@ -479,51 +496,287 @@ async function _readStream(stream) {
479
496
  return Buffer.concat(chunks);
480
497
  }
481
498
 
482
- async function sendNodeResponse(nodeRes, webRes) {
483
- if (!webRes) {
484
- nodeRes.statusCode = 500;
485
- return endNodeResponse(nodeRes);
486
- }
487
- if (webRes.xNodeResponse) {
488
- const res = webRes.xNodeResponse();
489
- nodeRes.writeHead(res.status, res.statusText, res.headers);
490
- if (res.body instanceof ReadableStream) {
491
- return streamBody(res.body, nodeRes).finally(
492
- () => endNodeResponse(nodeRes)
493
- );
499
+ const NodeFastResponse = /* @__PURE__ */ (() => (
500
+ /**
501
+ * Fast Response for Node.js runtime
502
+ *
503
+ * It is faster because in most cases it doesn't create a full Response instance.
504
+ */
505
+ class NodeFastResponse {
506
+ #body;
507
+ #init;
508
+ constructor(body, init) {
509
+ this.#body = body;
510
+ this.#init = init;
511
+ }
512
+ /**
513
+ * Prepare Node.js response object
514
+ */
515
+ nodeResponse() {
516
+ const status = this.#init?.status ?? 200;
517
+ const statusText = this.#init?.statusText ?? "";
518
+ const headers = [];
519
+ let headersInit = this.#init?.headers;
520
+ if (headersInit) {
521
+ if (typeof headersInit === "object") {
522
+ headersInit = Object.entries(headersInit);
523
+ }
524
+ for (const [key, value] of headersInit) {
525
+ if (key === "set-cookie") {
526
+ for (const setCookie of splitSetCookieString(value)) {
527
+ headers.push(["set-cookie", setCookie]);
528
+ }
529
+ } else {
530
+ headers.push([key, value]);
531
+ }
532
+ }
533
+ }
534
+ const bodyInit = this.#body;
535
+ let body;
536
+ if (bodyInit) {
537
+ if (typeof bodyInit === "string") {
538
+ body = bodyInit;
539
+ } else if (bodyInit instanceof ReadableStream) {
540
+ body = bodyInit;
541
+ } else if (bodyInit instanceof ArrayBuffer) {
542
+ body = Buffer.from(bodyInit);
543
+ } else if (bodyInit instanceof Uint8Array) {
544
+ body = Buffer.from(bodyInit);
545
+ } else if (bodyInit instanceof DataView) {
546
+ body = Buffer.from(bodyInit.buffer);
547
+ } else if (bodyInit instanceof Blob) {
548
+ body = bodyInit.stream();
549
+ if (bodyInit.type) {
550
+ headers.push(["content-type", bodyInit.type]);
551
+ }
552
+ } else {
553
+ const res = new Response(bodyInit);
554
+ body = res.body;
555
+ for (const [key, value] of res.headers) {
556
+ headers.push([key, value]);
557
+ }
558
+ }
559
+ }
560
+ this.#body = void 0;
561
+ this.#init = void 0;
562
+ return {
563
+ status,
564
+ statusText,
565
+ headers,
566
+ body
567
+ };
568
+ }
569
+ // ... the rest is for interface compatibility only and usually not to be used ...
570
+ /** Lazy initialized response instance */
571
+ #responseObj;
572
+ /** Lazy initialized headers instance */
573
+ #headersObj;
574
+ clone() {
575
+ if (this.#responseObj) {
576
+ return this.#responseObj.clone();
577
+ }
578
+ return new Response(this.#body, this.#init);
579
+ }
580
+ get #response() {
581
+ if (!this.#responseObj) {
582
+ this.#responseObj = new Response(this.#body, this.#init);
583
+ this.#body = void 0;
584
+ this.#init = void 0;
585
+ this.#headersObj = void 0;
586
+ }
587
+ return this.#responseObj;
494
588
  }
495
- nodeRes.write(res.body);
496
- return endNodeResponse(nodeRes);
497
- }
498
- const headerEntries = [];
499
- for (const [key, value] of webRes.headers) {
500
- if (key === "set-cookie") {
501
- for (const setCookie of splitSetCookieString(value)) {
502
- headerEntries.push(["set-cookie", setCookie]);
589
+ get headers() {
590
+ if (this.#responseObj) {
591
+ return this.#responseObj.headers;
503
592
  }
504
- } else {
505
- headerEntries.push([key, value]);
593
+ if (!this.#headersObj) {
594
+ this.#headersObj = new Headers(this.#init?.headers);
595
+ }
596
+ return this.#headersObj;
597
+ }
598
+ get ok() {
599
+ if (this.#responseObj) {
600
+ return this.#responseObj.ok;
601
+ }
602
+ const status = this.#init?.status ?? 200;
603
+ return status >= 200 && status < 300;
604
+ }
605
+ get redirected() {
606
+ if (this.#responseObj) {
607
+ return this.#responseObj.redirected;
608
+ }
609
+ return false;
610
+ }
611
+ get status() {
612
+ if (this.#responseObj) {
613
+ return this.#responseObj.status;
614
+ }
615
+ return this.#init?.status ?? 200;
616
+ }
617
+ get statusText() {
618
+ if (this.#responseObj) {
619
+ return this.#responseObj.statusText;
620
+ }
621
+ return this.#init?.statusText ?? "";
622
+ }
623
+ get type() {
624
+ if (this.#responseObj) {
625
+ return this.#responseObj.type;
626
+ }
627
+ return "default";
628
+ }
629
+ get url() {
630
+ if (this.#responseObj) {
631
+ return this.#responseObj.url;
632
+ }
633
+ return "";
634
+ }
635
+ // --- body ---
636
+ #fastBody(as) {
637
+ const bodyInit = this.#body;
638
+ if (bodyInit === null || bodyInit === void 0) {
639
+ return null;
640
+ }
641
+ if (bodyInit instanceof as) {
642
+ return bodyInit;
643
+ }
644
+ return false;
645
+ }
646
+ get body() {
647
+ if (this.#responseObj) {
648
+ return this.#responseObj.body;
649
+ }
650
+ const fastBody = this.#fastBody(ReadableStream);
651
+ if (fastBody !== false) {
652
+ return fastBody;
653
+ }
654
+ return this.#response.body;
655
+ }
656
+ get bodyUsed() {
657
+ if (this.#responseObj) {
658
+ return this.#responseObj.bodyUsed;
659
+ }
660
+ return false;
661
+ }
662
+ arrayBuffer() {
663
+ if (this.#responseObj) {
664
+ return this.#responseObj.arrayBuffer();
665
+ }
666
+ const fastBody = this.#fastBody(ArrayBuffer);
667
+ if (fastBody !== false) {
668
+ return Promise.resolve(fastBody || new ArrayBuffer(0));
669
+ }
670
+ return this.#response.arrayBuffer();
671
+ }
672
+ blob() {
673
+ if (this.#responseObj) {
674
+ return this.#responseObj.blob();
675
+ }
676
+ const fastBody = this.#fastBody(Blob);
677
+ if (fastBody !== false) {
678
+ return Promise.resolve(fastBody || new Blob());
679
+ }
680
+ return this.#response.blob();
681
+ }
682
+ bytes() {
683
+ if (this.#responseObj) {
684
+ return this.#responseObj.bytes();
685
+ }
686
+ const fastBody = this.#fastBody(Uint8Array);
687
+ if (fastBody !== false) {
688
+ return Promise.resolve(fastBody || new Uint8Array());
689
+ }
690
+ return this.#response.bytes();
691
+ }
692
+ formData() {
693
+ if (this.#responseObj) {
694
+ return this.#responseObj.formData();
695
+ }
696
+ const fastBody = this.#fastBody(FormData);
697
+ if (fastBody !== false) {
698
+ return Promise.resolve(fastBody || new FormData());
699
+ }
700
+ return this.#response.formData();
701
+ }
702
+ text() {
703
+ if (this.#responseObj) {
704
+ return this.#responseObj.text();
705
+ }
706
+ const bodyInit = this.#body;
707
+ if (bodyInit === null || bodyInit === void 0) {
708
+ return Promise.resolve("");
709
+ }
710
+ if (typeof bodyInit === "string") {
711
+ return Promise.resolve(bodyInit);
712
+ }
713
+ return this.#response.text();
714
+ }
715
+ json() {
716
+ if (this.#responseObj) {
717
+ return this.#responseObj.json();
718
+ }
719
+ return this.text().then((text) => JSON.parse(text));
506
720
  }
507
721
  }
508
- nodeRes.writeHead(webRes.status || 200, webRes.statusText, headerEntries);
509
- return webRes.body ? streamBody(webRes.body, nodeRes).finally(() => endNodeResponse(nodeRes)) : endNodeResponse(nodeRes);
510
- }
511
- function endNodeResponse(nodeRes) {
512
- return new Promise((resolve) => nodeRes.end(resolve));
722
+ ))();
723
+
724
+ function serve(options) {
725
+ return new NodeServer(options);
513
726
  }
514
- async function streamBody(stream, nodeRes) {
515
- const reader = stream.getReader();
516
- try {
517
- while (true) {
518
- const { done, value } = await reader.read();
519
- if (done) {
520
- break;
521
- }
522
- nodeRes.write(value);
727
+ class NodeServer {
728
+ constructor(options) {
729
+ this.runtime = "node";
730
+ this.options = options;
731
+ const fetchHandler = wrapFetch(this, this.options.fetch);
732
+ this.fetch = fetchHandler;
733
+ const handler = (nodeReq, nodeRes) => {
734
+ const request = new NodeRequestProxy(nodeReq);
735
+ request.node = { req: nodeReq, res: nodeRes };
736
+ const res = fetchHandler(request);
737
+ return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
738
+ };
739
+ this.serveOptions = {
740
+ port: resolvePort(this.options.port, globalThis.process?.env.PORT),
741
+ host: this.options.hostname,
742
+ exclusive: !this.options.reusePort,
743
+ ...this.options.node
744
+ };
745
+ const server = NodeHttp.createServer(this.serveOptions, handler);
746
+ this.node = { server, handler };
747
+ if (!options.manual) {
748
+ this.serve();
749
+ }
750
+ }
751
+ #listeningPromise;
752
+ serve() {
753
+ if (this.#listeningPromise) {
754
+ return Promise.resolve(this.#listeningPromise).then(() => this);
755
+ }
756
+ this.#listeningPromise = new Promise((resolve) => {
757
+ this.node.server.listen(this.serveOptions, () => resolve());
758
+ });
759
+ }
760
+ get url() {
761
+ const addr = this.node?.server?.address();
762
+ if (!addr) {
763
+ return;
523
764
  }
524
- } finally {
525
- reader.releaseLock();
765
+ return typeof addr === "string" ? addr : fmtURL(addr.address, addr.port);
766
+ }
767
+ ready() {
768
+ return Promise.resolve(this.#listeningPromise).then(() => this);
769
+ }
770
+ close(closeAll) {
771
+ return new Promise((resolve, reject) => {
772
+ if (closeAll) {
773
+ this.node?.server?.closeAllConnections?.();
774
+ }
775
+ this.node?.server?.close(
776
+ (error) => error ? reject(error) : resolve()
777
+ );
778
+ });
526
779
  }
527
780
  }
528
781
 
529
- export { NodeRequestProxy as N, NodeReqHeadersProxy as a, NodeResHeadersProxy as b, sendNodeResponse as s };
782
+ export { NodeFastResponse as Response, serve };
@@ -0,0 +1,76 @@
1
+ function wrapFetch(server, fetchHandler) {
2
+ const plugins = server.options.plugins;
3
+ if (!plugins?.length) {
4
+ return fetchHandler;
5
+ }
6
+ const requestHooks = [];
7
+ const responseHooks = [];
8
+ for (const ctor of plugins) {
9
+ const plugin = typeof ctor === "function" ? ctor(server) : ctor;
10
+ if (plugin.request) {
11
+ requestHooks.push(plugin.request);
12
+ }
13
+ if (plugin.response) {
14
+ responseHooks.push(plugin.response);
15
+ }
16
+ }
17
+ const hasRequestHooks = requestHooks.length > 0;
18
+ const hasResponseHooks = responseHooks.length > 0;
19
+ if (!hasRequestHooks && !hasResponseHooks) {
20
+ return fetchHandler;
21
+ }
22
+ return (request) => {
23
+ let resValue;
24
+ let resPromise;
25
+ if (hasRequestHooks) {
26
+ for (const reqHook of requestHooks) {
27
+ if (resPromise) {
28
+ resPromise = resPromise.then((res) => res || reqHook(request));
29
+ } else {
30
+ const res = reqHook(request);
31
+ if (res) {
32
+ if (res instanceof Promise) {
33
+ resPromise = res;
34
+ } else {
35
+ return res;
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ if (resPromise) {
42
+ resPromise = resPromise.then((res) => res || fetchHandler(request));
43
+ } else {
44
+ const res = fetchHandler(request);
45
+ if (res instanceof Promise) {
46
+ resPromise = res;
47
+ } else {
48
+ resValue = res;
49
+ }
50
+ }
51
+ if (hasResponseHooks) {
52
+ for (const resHook of responseHooks) {
53
+ if (resPromise) {
54
+ resPromise = resPromise.then((res) => {
55
+ if (res) {
56
+ resValue = res;
57
+ }
58
+ return resHook(request, resValue);
59
+ });
60
+ } else {
61
+ const res = resHook(request, resValue);
62
+ if (res) {
63
+ if (res instanceof Promise) {
64
+ resPromise = res;
65
+ } else {
66
+ resValue = res;
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ return resPromise ? resPromise.then((res) => res || resValue) : resValue;
73
+ };
74
+ }
75
+
76
+ export { wrapFetch as w };
@@ -0,0 +1,18 @@
1
+ function resolvePort(portOptions, portEnv) {
2
+ const portInput = portOptions ?? portEnv;
3
+ if (portInput === void 0) {
4
+ return 3e3;
5
+ }
6
+ return typeof portInput === "number" ? portInput : Number.parseInt(portInput, 10);
7
+ }
8
+ function fmtURL(host, port, ssl) {
9
+ if (!host || !port) {
10
+ return void 0;
11
+ }
12
+ if (host.includes(":")) {
13
+ host = `[${host}]`;
14
+ }
15
+ return `http${""}://${host}:${port}/`;
16
+ }
17
+
18
+ export { fmtURL as f, resolvePort as r };