srvx 0.5.1 → 0.6.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,11 +1,12 @@
1
1
  import { ServerOptions, Server, BunFetchHandler } from '../types.mjs';
2
2
  import * as Bun from 'bun';
3
+ export { F as FastURL } from '../shared/srvx.DEE2RO4O.mjs';
3
4
  import 'node:http';
4
5
  import 'node:https';
5
6
  import 'node:net';
6
7
  import '@cloudflare/workers-types';
7
8
 
8
- declare const Response: typeof globalThis.Response;
9
+ declare const FastResponse: typeof globalThis.Response;
9
10
  declare function serve(options: ServerOptions): BunServer;
10
11
  declare class BunServer implements Server<BunFetchHandler> {
11
12
  readonly runtime = "bun";
@@ -20,4 +21,4 @@ declare class BunServer implements Server<BunFetchHandler> {
20
21
  close(closeAll?: boolean): Promise<void>;
21
22
  }
22
23
 
23
- export { Response, serve };
24
+ export { FastResponse, serve };
@@ -1,8 +1,10 @@
1
- import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
2
- import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
1
+ import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.Ctaz0clH.mjs';
2
+ export { F as FastURL } from '../shared/srvx.Ctaz0clH.mjs';
3
+ import { w as wrapFetch } from '../shared/srvx.zEohKxBQ.mjs';
4
+ import { w as wsUpgradePlugin } from '../shared/srvx.BMykKwGg.mjs';
3
5
  import 'node:fs';
4
6
 
5
- const Response = globalThis.Response;
7
+ const FastResponse = Response;
6
8
  function serve(options) {
7
9
  return new BunServer(options);
8
10
  }
@@ -11,7 +13,7 @@ class BunServer {
11
13
  this.runtime = "bun";
12
14
  this.bun = {};
13
15
  this.options = options;
14
- const fetchHandler = wrapFetch(this, this.options.fetch);
16
+ const fetchHandler = wrapFetch(this, [wsUpgradePlugin]);
15
17
  this.fetch = (request, server) => {
16
18
  Object.defineProperties(request, {
17
19
  runtime: {
@@ -31,7 +33,7 @@ class BunServer {
31
33
  this.serveOptions = {
32
34
  ...resolvePortAndHost(this.options),
33
35
  reusePort: this.options.reusePort,
34
- error: this.options.onError,
36
+ error: this.options.error,
35
37
  ...this.options.bun,
36
38
  tls: {
37
39
  cert: tls?.cert,
@@ -75,4 +77,4 @@ class BunServer {
75
77
  }
76
78
  }
77
79
 
78
- export { Response, serve };
80
+ export { FastResponse, serve };
@@ -5,7 +5,8 @@ import 'node:https';
5
5
  import 'node:net';
6
6
  import 'bun';
7
7
 
8
- declare const Response: typeof globalThis.Response;
8
+ declare const FastURL: typeof globalThis.URL;
9
+ declare const FastResponse: typeof globalThis.Response;
9
10
  declare function serve(options: ServerOptions): Server<CF.ExportedHandlerFetchHandler>;
10
11
 
11
- export { Response, serve };
12
+ export { FastResponse, FastURL, serve };
@@ -1,7 +1,9 @@
1
- import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
2
- import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
1
+ import { w as wrapFetch } from '../shared/srvx.zEohKxBQ.mjs';
2
+ import { e as errorPlugin } from '../shared/srvx.CEIXM-sv.mjs';
3
+ import { w as wsUpgradePlugin } from '../shared/srvx.BMykKwGg.mjs';
3
4
 
4
- const Response = globalThis.Response;
5
+ const FastURL = URL;
6
+ const FastResponse = Response;
5
7
  function serve(options) {
6
8
  return new CloudflareServer(options);
7
9
  }
@@ -9,10 +11,10 @@ class CloudflareServer {
9
11
  constructor(options) {
10
12
  this.runtime = "cloudflare";
11
13
  this.options = options;
12
- const fetchHandler = wrapFetch(
13
- this,
14
- wrapFetchOnError(this.options.fetch, this.options.onError)
15
- );
14
+ const fetchHandler = wrapFetch(this, [
15
+ errorPlugin,
16
+ wsUpgradePlugin
17
+ ]);
16
18
  this.fetch = (request, env, context) => {
17
19
  Object.defineProperties(request, {
18
20
  runtime: {
@@ -49,4 +51,4 @@ class CloudflareServer {
49
51
  }
50
52
  }
51
53
 
52
- export { Response, serve };
54
+ export { FastResponse, FastURL, serve };
@@ -1,11 +1,12 @@
1
1
  import { ServerOptions, Server, DenoFetchHandler } from '../types.mjs';
2
+ export { F as FastURL } from '../shared/srvx.DEE2RO4O.mjs';
2
3
  import 'node:http';
3
4
  import 'node:https';
4
5
  import 'node:net';
5
6
  import 'bun';
6
7
  import '@cloudflare/workers-types';
7
8
 
8
- declare const Response: typeof globalThis.Response;
9
+ declare const FastResponse: typeof globalThis.Response;
9
10
  declare function serve(options: ServerOptions): DenoServer;
10
11
  declare class DenoServer implements Server<DenoFetchHandler> {
11
12
  #private;
@@ -21,4 +22,4 @@ declare class DenoServer implements Server<DenoFetchHandler> {
21
22
  close(): Promise<void | undefined>;
22
23
  }
23
24
 
24
- export { Response, serve };
25
+ export { FastResponse, serve };
@@ -1,8 +1,10 @@
1
- import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
2
- import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
1
+ import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.Ctaz0clH.mjs';
2
+ export { F as FastURL } from '../shared/srvx.Ctaz0clH.mjs';
3
+ import { w as wrapFetch } from '../shared/srvx.zEohKxBQ.mjs';
4
+ import { w as wsUpgradePlugin } from '../shared/srvx.BMykKwGg.mjs';
3
5
  import 'node:fs';
4
6
 
5
- const Response = globalThis.Response;
7
+ const FastResponse = Response;
6
8
  function serve(options) {
7
9
  return new DenoServer(options);
8
10
  }
@@ -11,7 +13,7 @@ class DenoServer {
11
13
  this.runtime = "deno";
12
14
  this.deno = {};
13
15
  this.options = options;
14
- const fetchHandler = wrapFetch(this, this.options.fetch);
16
+ const fetchHandler = wrapFetch(this, [wsUpgradePlugin]);
15
17
  this.fetch = (request, info) => {
16
18
  Object.defineProperties(request, {
17
19
  runtime: {
@@ -31,7 +33,7 @@ class DenoServer {
31
33
  this.serveOptions = {
32
34
  ...resolvePortAndHost(this.options),
33
35
  reusePort: this.options.reusePort,
34
- onError: this.options.onError,
36
+ onError: this.options.error,
35
37
  ...tls ? { key: tls.key, cert: tls.cert, passphrase: tls.passphrase } : {},
36
38
  ...this.options.deno
37
39
  };
@@ -78,4 +80,4 @@ class DenoServer {
78
80
  }
79
81
  }
80
82
 
81
- export { Response, serve };
83
+ export { FastResponse, serve };
@@ -5,7 +5,8 @@ import 'node:net';
5
5
  import 'bun';
6
6
  import '@cloudflare/workers-types';
7
7
 
8
- declare const Response: typeof globalThis.Response;
8
+ declare const FastURL: typeof globalThis.URL;
9
+ declare const FastResponse: typeof globalThis.Response;
9
10
  declare function serve(options: ServerOptions): Server;
10
11
 
11
- export { Response, serve };
12
+ export { FastResponse, FastURL, serve };
@@ -1,7 +1,8 @@
1
- import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
2
- import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
1
+ import { w as wrapFetch } from '../shared/srvx.zEohKxBQ.mjs';
2
+ import { e as errorPlugin } from '../shared/srvx.CEIXM-sv.mjs';
3
3
 
4
- const Response = globalThis.Response;
4
+ const FastURL = URL;
5
+ const FastResponse = Response;
5
6
  function serve(options) {
6
7
  return new GenericServer(options);
7
8
  }
@@ -9,10 +10,7 @@ class GenericServer {
9
10
  constructor(options) {
10
11
  this.runtime = "generic";
11
12
  this.options = options;
12
- const fetchHandler = wrapFetch(
13
- this,
14
- wrapFetchOnError(this.options.fetch, this.options.onError)
15
- );
13
+ const fetchHandler = wrapFetch(this, [errorPlugin]);
16
14
  this.fetch = (request) => {
17
15
  return Promise.resolve(fetchHandler(request));
18
16
  };
@@ -27,4 +25,4 @@ class GenericServer {
27
25
  }
28
26
  }
29
27
 
30
- export { Response, serve };
28
+ export { FastResponse, FastURL, serve };
@@ -1,6 +1,7 @@
1
1
  import { ServerRequest, ServerOptions, Server, FetchHandler, NodeHttpHandler } from '../types.mjs';
2
+ export { F as FastURL } from '../shared/srvx.DEE2RO4O.mjs';
2
3
  import NodeHttp__default from 'node:http';
3
- import { Readable } from 'node:stream';
4
+ import NodeStream, { Readable } from 'node:stream';
4
5
  import 'node:https';
5
6
  import 'node:net';
6
7
  import 'bun';
@@ -23,11 +24,16 @@ declare const NodeResponse: {
23
24
  };
24
25
  };
25
26
 
27
+ type NodeRequestContext = {
28
+ req: NodeHttp__default.IncomingMessage;
29
+ res?: NodeHttp__default.ServerResponse;
30
+ upgrade?: {
31
+ socket: NodeStream.Duplex;
32
+ header: Buffer;
33
+ };
34
+ };
26
35
  declare const NodeRequest: {
27
- new (nodeCtx: {
28
- req: NodeHttp__default.IncomingMessage;
29
- res: NodeHttp__default.ServerResponse;
30
- }): ServerRequest;
36
+ new (nodeCtx: NodeRequestContext): ServerRequest;
31
37
  };
32
38
 
33
39
  declare const NodeRequestHeaders: {
@@ -46,4 +52,4 @@ declare const NodeResponseHeaders: {
46
52
  declare function serve(options: ServerOptions): Server;
47
53
  declare function toNodeHandler(fetchHandler: FetchHandler): NodeHttpHandler;
48
54
 
49
- export { NodeRequest, NodeRequestHeaders, NodeResponse, NodeResponseHeaders, NodeRequest as Request, NodeResponse as Response, serve, toNodeHandler };
55
+ export { NodeResponse as FastResponse, NodeRequest, NodeRequestHeaders, NodeResponse, NodeResponseHeaders, serve, toNodeHandler };
@@ -1,9 +1,10 @@
1
1
  import NodeHttp from 'node:http';
2
2
  import NodeHttps from 'node:https';
3
3
  import { splitSetCookieString } from 'cookie-es';
4
- import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
5
- import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
6
- import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
4
+ import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.Ctaz0clH.mjs';
5
+ export { F as FastURL } from '../shared/srvx.Ctaz0clH.mjs';
6
+ import { w as wrapFetch } from '../shared/srvx.zEohKxBQ.mjs';
7
+ import { e as errorPlugin } from '../shared/srvx.CEIXM-sv.mjs';
7
8
  import 'node:fs';
8
9
 
9
10
  async function sendNodeResponse(nodeRes, webRes) {
@@ -46,6 +47,23 @@ async function sendNodeResponse(nodeRes, webRes) {
46
47
  }
47
48
  return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
48
49
  }
50
+ async function sendNodeUpgradeResponse(socket, res) {
51
+ const head = [
52
+ `HTTP/1.1 ${res.status || 200} ${res.statusText || ""}`,
53
+ ...[...res.headers.entries()].map(
54
+ ([key, value]) => `${encodeURIComponent(key)}: ${encodeURIComponent(value)}`
55
+ )
56
+ ];
57
+ socket.write(head.join("\r\n") + "\r\n\r\n");
58
+ if (res.body) {
59
+ for await (const chunk of res.body) {
60
+ socket.write(chunk);
61
+ }
62
+ }
63
+ return new Promise((resolve) => {
64
+ socket.end(resolve);
65
+ });
66
+ }
49
67
  function endNodeResponse(nodeRes) {
50
68
  return new Promise((resolve) => nodeRes.end(resolve));
51
69
  }
@@ -688,7 +706,7 @@ const NodeResponse = /* @__PURE__ */ (() => {
688
706
  const headers = [];
689
707
  const headersInit = this.#init?.headers;
690
708
  if (headersInit) {
691
- const headerEntries = headersInit.entries ? headersInit.entries() : Object.entries(headersInit);
709
+ const headerEntries = Array.isArray(headersInit) ? headersInit : headersInit.entries ? headersInit.entries() : Object.entries(headersInit);
692
710
  for (const [key, value] of headerEntries) {
693
711
  if (key === "set-cookie") {
694
712
  for (const setCookie of splitSetCookieString(value)) {
@@ -699,6 +717,17 @@ const NodeResponse = /* @__PURE__ */ (() => {
699
717
  }
700
718
  }
701
719
  }
720
+ if (this.#headersObj) {
721
+ for (const [key, value] of this.#headersObj) {
722
+ if (key === "set-cookie") {
723
+ for (const setCookie of splitSetCookieString(value)) {
724
+ headers.push(["set-cookie", setCookie]);
725
+ }
726
+ } else {
727
+ headers.push([key, value]);
728
+ }
729
+ }
730
+ }
702
731
  const bodyInit = this.#body;
703
732
  let body;
704
733
  if (bodyInit) {
@@ -907,11 +936,7 @@ class NodeServer {
907
936
  constructor(options) {
908
937
  this.runtime = "node";
909
938
  this.options = options;
910
- const fetchHandler = wrapFetch(
911
- this,
912
- wrapFetchOnError(this.options.fetch, this.options.onError)
913
- );
914
- this.fetch = fetchHandler;
939
+ const fetchHandler = this.fetch = wrapFetch(this, [errorPlugin]);
915
940
  const handler = (nodeReq, nodeRes) => {
916
941
  const request = new NodeRequest({ req: nodeReq, res: nodeRes });
917
942
  const res = fetchHandler(request);
@@ -930,6 +955,19 @@ class NodeServer {
930
955
  this.serveOptions,
931
956
  handler
932
957
  ) : NodeHttp.createServer(this.serveOptions, handler);
958
+ const upgradeHandler = this.options.upgrade;
959
+ if (upgradeHandler) {
960
+ server.on("upgrade", (nodeReq, socket, header) => {
961
+ const request = new NodeRequest({
962
+ req: nodeReq,
963
+ upgrade: { socket, header }
964
+ });
965
+ const res = upgradeHandler(request);
966
+ return res instanceof Promise ? res.then(
967
+ (resolvedRes) => sendNodeUpgradeResponse(socket, resolvedRes)
968
+ ) : sendNodeUpgradeResponse(socket, res);
969
+ });
970
+ }
933
971
  this.node = { server, handler };
934
972
  if (!options.manual) {
935
973
  this.serve();
@@ -973,4 +1011,4 @@ class NodeServer {
973
1011
  }
974
1012
  }
975
1013
 
976
- export { NodeRequest, NodeRequestHeaders, NodeResponse, NodeResponseHeaders, NodeRequest as Request, NodeResponse as Response, serve, toNodeHandler };
1014
+ export { NodeResponse as FastResponse, NodeRequest, NodeRequestHeaders, NodeResponse, NodeResponseHeaders, serve, toNodeHandler };
@@ -5,9 +5,10 @@ import 'node:net';
5
5
  import 'bun';
6
6
  import '@cloudflare/workers-types';
7
7
 
8
- declare const Response: typeof globalThis.Response;
8
+ declare const FastURL: typeof globalThis.URL;
9
+ declare const FastResponse: typeof globalThis.Response;
9
10
  type ServiceWorkerHandler = (request: ServerRequest, event: FetchEvent) => Response | Promise<Response>;
10
11
  declare function serve(options: ServerOptions): Server<ServiceWorkerHandler>;
11
12
 
12
- export { Response, serve };
13
+ export { FastResponse, FastURL, serve };
13
14
  export type { ServiceWorkerHandler };
@@ -1,7 +1,8 @@
1
- import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
2
- import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
1
+ import { w as wrapFetch } from '../shared/srvx.zEohKxBQ.mjs';
2
+ import { e as errorPlugin } from '../shared/srvx.CEIXM-sv.mjs';
3
3
 
4
- const Response = globalThis.Response;
4
+ const FastURL = URL;
5
+ const FastResponse = Response;
5
6
  const isBrowserWindow = typeof window !== "undefined" && typeof navigator !== "undefined";
6
7
  const isServiceWorker = typeof self !== "undefined" && "skipWaiting" in self;
7
8
  function serve(options) {
@@ -11,10 +12,7 @@ class ServiceWorkerServer {
11
12
  constructor(options) {
12
13
  this.runtime = "service-worker";
13
14
  this.options = options;
14
- const fetchHandler = wrapFetch(
15
- this,
16
- wrapFetchOnError(this.options.fetch, this.options.onError)
17
- );
15
+ const fetchHandler = wrapFetch(this, [errorPlugin]);
18
16
  this.fetch = (request, event) => {
19
17
  Object.defineProperties(request, {
20
18
  runtime: {
@@ -94,4 +92,4 @@ class ServiceWorkerServer {
94
92
  }
95
93
  }
96
94
 
97
- export { Response, serve };
95
+ export { FastResponse, FastURL, serve };
@@ -0,0 +1,16 @@
1
+ const wsUpgradePlugin = (server) => {
2
+ const upgradeHandler = server.options.upgrade;
3
+ if (!upgradeHandler) {
4
+ return {};
5
+ }
6
+ return {
7
+ fetch(request, next) {
8
+ if (request.headers.get("upgrade") === "websocket") {
9
+ return upgradeHandler(request);
10
+ }
11
+ return next();
12
+ }
13
+ };
14
+ };
15
+
16
+ export { wsUpgradePlugin as w };
@@ -0,0 +1,18 @@
1
+ const errorPlugin = (server) => {
2
+ const errorHandler = server.options.error;
3
+ if (!errorHandler) {
4
+ return {};
5
+ }
6
+ return {
7
+ fetch(_request, next) {
8
+ try {
9
+ const res = next();
10
+ return res instanceof Promise ? res.catch((error) => errorHandler(error)) : res;
11
+ } catch (error) {
12
+ return errorHandler(error);
13
+ }
14
+ }
15
+ };
16
+ };
17
+
18
+ export { errorPlugin as e };
@@ -0,0 +1,172 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ function resolvePortAndHost(opts) {
4
+ const _port = opts.port ?? globalThis.process?.env.PORT ?? 3e3;
5
+ const port = typeof _port === "number" ? _port : Number.parseInt(_port, 10);
6
+ const hostname = opts.hostname ?? globalThis.process?.env.HOST;
7
+ return { port, hostname };
8
+ }
9
+ function fmtURL(host, port, secure) {
10
+ if (!host || !port) {
11
+ return void 0;
12
+ }
13
+ if (host.includes(":")) {
14
+ host = `[${host}]`;
15
+ }
16
+ return `http${secure ? "s" : ""}://${host}:${port}/`;
17
+ }
18
+ function printListening(opts, url) {
19
+ if (!url || (opts.silent ?? globalThis.process?.env?.TEST)) {
20
+ return;
21
+ }
22
+ const _url = new URL(url);
23
+ const allInterfaces = _url.hostname === "[::]" || _url.hostname === "0.0.0.0";
24
+ if (allInterfaces) {
25
+ _url.hostname = "localhost";
26
+ url = _url.href;
27
+ }
28
+ let listeningOn = `\u279C Listening on:`;
29
+ let additionalInfo = allInterfaces ? " (all interfaces)" : "";
30
+ if (globalThis.process.stdout?.isTTY) {
31
+ listeningOn = `\x1B[32m${listeningOn}\x1B[0m`;
32
+ url = `\x1B[36m${url}\x1B[0m`;
33
+ additionalInfo = `\x1B[2m${additionalInfo}\x1B[0m`;
34
+ }
35
+ console.log(` ${listeningOn} ${url}${additionalInfo}`);
36
+ }
37
+ function resolveTLSOptions(opts) {
38
+ if (!opts.tls || opts.protocol === "http") {
39
+ return;
40
+ }
41
+ const cert = resolveCertOrKey(opts.tls.cert);
42
+ const key = resolveCertOrKey(opts.tls.key);
43
+ if (!cert && !key) {
44
+ if (opts.protocol === "https") {
45
+ throw new TypeError(
46
+ "TLS `cert` and `key` must be provided for `https` protocol."
47
+ );
48
+ }
49
+ return;
50
+ }
51
+ if (!cert || !key) {
52
+ throw new TypeError("TLS `cert` and `key` must be provided together.");
53
+ }
54
+ return {
55
+ cert,
56
+ key,
57
+ passphrase: opts.tls.passphrase
58
+ };
59
+ }
60
+ function resolveCertOrKey(value) {
61
+ if (!value) {
62
+ return;
63
+ }
64
+ if (typeof value !== "string") {
65
+ throw new TypeError(
66
+ "TLS certificate and key must be strings in PEM format or file paths."
67
+ );
68
+ }
69
+ if (value.startsWith("-----BEGIN ")) {
70
+ return value;
71
+ }
72
+ return readFileSync(value, "utf8");
73
+ }
74
+
75
+ const FastURL = /* @__PURE__ */ (() => {
76
+ const FastURL2 = class URL {
77
+ #originalURL;
78
+ #parsedURL;
79
+ constructor(url) {
80
+ this.#originalURL = url;
81
+ }
82
+ get _url() {
83
+ if (!this.#parsedURL) {
84
+ this.#parsedURL = new globalThis.URL(this.#originalURL);
85
+ }
86
+ return this.#parsedURL;
87
+ }
88
+ toString() {
89
+ return this._url.toString();
90
+ }
91
+ toJSON() {
92
+ return this.toString();
93
+ }
94
+ get pathname() {
95
+ if (this.#parsedURL) {
96
+ return this.#parsedURL.pathname;
97
+ }
98
+ if (!this._pathname) {
99
+ const url = this.#originalURL;
100
+ const protoIndex = url.indexOf("://");
101
+ if (protoIndex === -1) {
102
+ return this._url.pathname;
103
+ }
104
+ const pIndex = url.indexOf(
105
+ "/",
106
+ protoIndex + 4
107
+ /* :// */
108
+ );
109
+ if (pIndex === -1) {
110
+ return this._url.pathname;
111
+ }
112
+ const qIndex = this._urlqindex = url.indexOf("?", pIndex);
113
+ this._pathname = url.slice(pIndex, qIndex === -1 ? void 0 : qIndex);
114
+ }
115
+ return this._pathname;
116
+ }
117
+ set pathname(value) {
118
+ this._url.pathname = value;
119
+ }
120
+ get searchParams() {
121
+ if (this.#parsedURL) {
122
+ return this.#parsedURL.searchParams;
123
+ }
124
+ if (!this._query) {
125
+ this._query = new URLSearchParams(this.search);
126
+ }
127
+ return this._query;
128
+ }
129
+ get search() {
130
+ if (this.#parsedURL) {
131
+ return this.#parsedURL.search;
132
+ }
133
+ if (!this._search) {
134
+ const qIndex = this._urlqindex;
135
+ if (qIndex === -1) {
136
+ this._search = "";
137
+ } else {
138
+ this._search = qIndex === void 0 ? this._url.search : this.#originalURL.slice(this._urlqindex);
139
+ }
140
+ }
141
+ return this._search;
142
+ }
143
+ set search(value) {
144
+ this._url.search = value;
145
+ }
146
+ };
147
+ const slowProps = [
148
+ "hash",
149
+ "host",
150
+ "hostname",
151
+ "href",
152
+ "origin",
153
+ "password",
154
+ "port",
155
+ "protocol",
156
+ "username"
157
+ ];
158
+ for (const prop of slowProps) {
159
+ Object.defineProperty(FastURL2.prototype, prop, {
160
+ get() {
161
+ return this._url[prop];
162
+ },
163
+ set(value) {
164
+ this._url[prop] = value;
165
+ }
166
+ });
167
+ }
168
+ Object.setPrototypeOf(FastURL2, globalThis.URL);
169
+ return FastURL2;
170
+ })();
171
+
172
+ export { FastURL as F, resolvePortAndHost as a, fmtURL as f, printListening as p, resolveTLSOptions as r };
@@ -0,0 +1,5 @@
1
+ declare const FastURL: {
2
+ new (url: string): URL;
3
+ };
4
+
5
+ export { FastURL as F };
@@ -0,0 +1,20 @@
1
+ function wrapFetch(server, basePlugins) {
2
+ const plugins = [
3
+ ...basePlugins || [],
4
+ ...server.options.plugins || []
5
+ ].map((plugin) => typeof plugin === "function" ? plugin(server) : plugin);
6
+ const middleware = plugins.filter((plugin) => plugin.fetch).map((plugin) => plugin.fetch);
7
+ const fetchHandler = server.options.fetch;
8
+ return middleware.length === 0 ? fetchHandler : (request) => callMiddleware(request, fetchHandler, middleware, 0);
9
+ }
10
+ function callMiddleware(request, fetchHandler, middleware, index) {
11
+ if (index === middleware.length) {
12
+ return fetchHandler(request);
13
+ }
14
+ return middleware[index](
15
+ request,
16
+ () => callMiddleware(request, fetchHandler, middleware, index + 1)
17
+ );
18
+ }
19
+
20
+ export { wrapFetch as w };
package/dist/types.d.mts CHANGED
@@ -5,7 +5,14 @@ import * as Bun from 'bun';
5
5
  import * as CF from '@cloudflare/workers-types';
6
6
 
7
7
  type MaybePromise<T> = T | Promise<T>;
8
- declare const Response: typeof globalThis.Response;
8
+ /**
9
+ * Faster URL constructor with lazy access to pathname and search params (For Node, Deno, and Bun).
10
+ */
11
+ declare const FastURL: typeof globalThis.URL;
12
+ /**
13
+ * Faster Response constructor optimized for Node.js (same as Response for other runtimes).
14
+ */
15
+ declare const FastResponse: typeof globalThis.Response;
9
16
  /**
10
17
  * Create a new server instance.
11
18
  */
@@ -22,6 +29,16 @@ interface ServerOptions {
22
29
  * The fetch handler handles incoming requests.
23
30
  */
24
31
  fetch: ServerHandler;
32
+ /**
33
+ * Handle websocket upgrades.
34
+ */
35
+ upgrade?: ServerHandler;
36
+ /**
37
+ * Handle lifecycle errors.
38
+ *
39
+ * @note This handler will set built-in Bun and Deno error handler.
40
+ */
41
+ error?: ErrorHandler;
25
42
  /**
26
43
  * Server plugins.
27
44
  */
@@ -81,12 +98,6 @@ interface ServerOptions {
81
98
  */
82
99
  passphrase?: string;
83
100
  };
84
- /**
85
- * Runtime agnostic error handler (optional).
86
- *
87
- * @note This handler will take precedence over runtime specific error handlers.
88
- */
89
- onError?: ErrorHandler;
90
101
  /**
91
102
  * Node.js server options.
92
103
  */
@@ -169,21 +180,10 @@ interface Server<Handler = ServerHandler> {
169
180
  close(closeActiveConnections?: boolean): Promise<void>;
170
181
  }
171
182
  type ServerPlugin = (server: Server) => ServerPluginInstance;
183
+ type ServerMiddleware = (request: ServerRequest, next: () => Response | Promise<Response>) => Response | Promise<Response>;
172
184
  interface ServerPluginInstance {
173
- /**
174
- * Plugin display name
175
- */
176
185
  name?: string;
177
- /**
178
- * Hook to allow running logic before user fetch handler
179
- * If an response value is returned, user fetch handler and the next plugins will be skipped.
180
- */
181
- request?: (request: ServerRequest) => MaybePromise<Response | void>;
182
- /**
183
- * Hook to allow running logic after user fetch handler
184
- * If a response value is returned, user response and the next plugins will be skipped.
185
- */
186
- response?: (request: ServerRequest, response: Response) => MaybePromise<void | Response>;
186
+ fetch?: ServerMiddleware;
187
187
  }
188
188
  interface ServerRuntimeContext {
189
189
  name: "node" | "deno" | "bun" | "cloudflare" | (string & {});
@@ -231,5 +231,5 @@ type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.Net
231
231
  type NodeHttpHandler = (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
232
232
  type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
233
233
 
234
- export { Response, serve };
235
- export type { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FetchHandler, NodeHttpHandler, Server, ServerHandler, ServerOptions, ServerPlugin, ServerPluginInstance, ServerRequest, ServerRuntimeContext };
234
+ export { FastResponse, FastURL, serve };
235
+ export type { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FetchHandler, NodeHttpHandler, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerPluginInstance, ServerRequest, ServerRuntimeContext };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "srvx",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Universal Server API based on web platform standards. Works seamlessly with Deno, Bun and Node.js.",
5
- "repository": "h3js/srvx",
6
5
  "homepage": "https://srvx.h3.dev",
6
+ "repository": "h3js/srvx",
7
7
  "license": "MIT",
8
8
  "sideEffects": false,
9
9
  "type": "module",
@@ -31,10 +31,14 @@
31
31
  ],
32
32
  "scripts": {
33
33
  "bench:node": "node test/bench-node/_run.mjs",
34
+ "bench:url:bun": "bun run ./test/url.bench.ts",
35
+ "bench:url:deno": "deno run -A ./test/url.bench.ts",
36
+ "bench:url:node": "pnpm node-ts --expose-gc --allow-natives-syntax ./test/url.bench.ts",
34
37
  "build": "unbuild",
35
38
  "dev": "vitest dev",
36
39
  "lint": "eslint . && prettier -c .",
37
40
  "lint:fix": "automd && eslint . --fix && prettier -w .",
41
+ "node-ts": "node --disable-warning=ExperimentalWarning --experimental-strip-types",
38
42
  "prepack": "pnpm build",
39
43
  "play:bun": "bun playground/app.mjs",
40
44
  "play:cf": "pnpx wrangler dev playground/app.mjs",
@@ -55,6 +59,7 @@
55
59
  "devDependencies": {
56
60
  "@cloudflare/workers-types": "^4.20250423.0",
57
61
  "@hono/node-server": "^1.14.1",
62
+ "@mitata/counters": "^0.0.8",
58
63
  "@mjackson/node-fetch-server": "^0.6.1",
59
64
  "@types/bun": "^1.2.10",
60
65
  "@types/deno": "^2.2.0",
@@ -67,7 +72,7 @@
67
72
  "eslint-config-unjs": "^0.4.2",
68
73
  "execa": "^9.5.2",
69
74
  "get-port-please": "^3.1.2",
70
- "jiti": "^2.4.2",
75
+ "mitata": "^1.0.34",
71
76
  "prettier": "^3.5.3",
72
77
  "typescript": "^5.8.3",
73
78
  "unbuild": "^3.5.0",
@@ -1,16 +0,0 @@
1
- function wrapFetchOnError(fetchHandler, onError) {
2
- if (!onError) return fetchHandler;
3
- return (...params) => {
4
- try {
5
- const result = fetchHandler(...params);
6
- if (result instanceof Promise) {
7
- return result.catch(onError);
8
- }
9
- return result;
10
- } catch (error) {
11
- return onError(error);
12
- }
13
- };
14
- }
15
-
16
- export { wrapFetchOnError as w };
@@ -1,76 +0,0 @@
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 };
@@ -1,75 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
-
3
- function resolvePortAndHost(opts) {
4
- const _port = opts.port ?? globalThis.process?.env.PORT ?? 3e3;
5
- const port = typeof _port === "number" ? _port : Number.parseInt(_port, 10);
6
- const hostname = opts.hostname ?? globalThis.process?.env.HOST;
7
- return { port, hostname };
8
- }
9
- function fmtURL(host, port, secure) {
10
- if (!host || !port) {
11
- return void 0;
12
- }
13
- if (host.includes(":")) {
14
- host = `[${host}]`;
15
- }
16
- return `http${secure ? "s" : ""}://${host}:${port}/`;
17
- }
18
- function printListening(opts, url) {
19
- if (!url || (opts.silent ?? globalThis.process?.env?.TEST)) {
20
- return;
21
- }
22
- const _url = new URL(url);
23
- const allInterfaces = _url.hostname === "[::]" || _url.hostname === "0.0.0.0";
24
- if (allInterfaces) {
25
- _url.hostname = "localhost";
26
- url = _url.href;
27
- }
28
- let listeningOn = `\u279C Listening on:`;
29
- let additionalInfo = allInterfaces ? " (all interfaces)" : "";
30
- if (globalThis.process.stdout?.isTTY) {
31
- listeningOn = `\x1B[32m${listeningOn}\x1B[0m`;
32
- url = `\x1B[36m${url}\x1B[0m`;
33
- additionalInfo = `\x1B[2m${additionalInfo}\x1B[0m`;
34
- }
35
- console.log(` ${listeningOn} ${url}${additionalInfo}`);
36
- }
37
- function resolveTLSOptions(opts) {
38
- if (!opts.tls || opts.protocol === "http") {
39
- return;
40
- }
41
- const cert = resolveCertOrKey(opts.tls.cert);
42
- const key = resolveCertOrKey(opts.tls.key);
43
- if (!cert && !key) {
44
- if (opts.protocol === "https") {
45
- throw new TypeError(
46
- "TLS `cert` and `key` must be provided for `https` protocol."
47
- );
48
- }
49
- return;
50
- }
51
- if (!cert || !key) {
52
- throw new TypeError("TLS `cert` and `key` must be provided together.");
53
- }
54
- return {
55
- cert,
56
- key,
57
- passphrase: opts.tls.passphrase
58
- };
59
- }
60
- function resolveCertOrKey(value) {
61
- if (!value) {
62
- return;
63
- }
64
- if (typeof value !== "string") {
65
- throw new TypeError(
66
- "TLS certificate and key must be strings in PEM format or file paths."
67
- );
68
- }
69
- if (value.startsWith("-----BEGIN ")) {
70
- return value;
71
- }
72
- return readFileSync(value, "utf8");
73
- }
74
-
75
- export { resolvePortAndHost as a, fmtURL as f, printListening as p, resolveTLSOptions as r };