srvx 0.6.0 → 0.7.1

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/bun.d.mts ADDED
@@ -0,0 +1,22 @@
1
+ import { BunFetchHandler, Server, ServerOptions } from "./chunks/types-egi3HKdd.mjs";
2
+ import { FastURL$2 as FastURL } from "./chunks/_url-BwTFuoNW.mjs";
3
+ import * as bun from "bun";
4
+
5
+ //#region src/adapters/bun.d.ts
6
+ declare const FastResponse: typeof globalThis.Response;
7
+ declare function serve(options: ServerOptions): BunServer;
8
+ declare class BunServer implements Server<BunFetchHandler> {
9
+ readonly runtime = "bun";
10
+ readonly options: Server["options"];
11
+ readonly bun: Server["bun"];
12
+ readonly serveOptions: bun.ServeOptions | bun.TLSServeOptions;
13
+ readonly fetch: BunFetchHandler;
14
+ constructor(options: ServerOptions);
15
+ serve(): Promise<this>;
16
+ get url(): string | undefined;
17
+ ready(): Promise<this>;
18
+ close(closeAll?: boolean): Promise<void>;
19
+ }
20
+
21
+ //#endregion
22
+ export { FastResponse, FastURL, serve };
package/dist/bun.mjs ADDED
@@ -0,0 +1,77 @@
1
+ import { FastURL$1 as FastURL, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions } from "./chunks/_url-CirWh9Lc.mjs";
2
+ import { wrapFetch } from "./chunks/_middleware-DSFIayTI.mjs";
3
+
4
+ //#region src/adapters/bun.ts
5
+ const FastResponse = Response;
6
+ function serve(options) {
7
+ return new BunServer(options);
8
+ }
9
+ var BunServer = class {
10
+ runtime = "bun";
11
+ options;
12
+ bun = {};
13
+ serveOptions;
14
+ fetch;
15
+ constructor(options) {
16
+ this.options = {
17
+ ...options,
18
+ middleware: [...options.middleware || []]
19
+ };
20
+ for (const plugin of options.plugins || []) plugin(this);
21
+ const fetchHandler = wrapFetch(this);
22
+ this.fetch = (request, server) => {
23
+ Object.defineProperties(request, {
24
+ runtime: {
25
+ enumerable: true,
26
+ value: {
27
+ runtime: "bun",
28
+ bun: { server }
29
+ }
30
+ },
31
+ ip: {
32
+ enumerable: true,
33
+ get() {
34
+ return server?.requestIP(request)?.address;
35
+ }
36
+ }
37
+ });
38
+ return fetchHandler(request);
39
+ };
40
+ const tls = resolveTLSOptions(this.options);
41
+ this.serveOptions = {
42
+ ...resolvePortAndHost(this.options),
43
+ reusePort: this.options.reusePort,
44
+ error: this.options.error,
45
+ ...this.options.bun,
46
+ tls: {
47
+ cert: tls?.cert,
48
+ key: tls?.key,
49
+ passphrase: tls?.passphrase,
50
+ ...this.options.bun?.tls
51
+ },
52
+ fetch: this.fetch
53
+ };
54
+ if (!options.manual) this.serve();
55
+ }
56
+ serve() {
57
+ if (!this.bun.server) this.bun.server = Bun.serve(this.serveOptions);
58
+ printListening(this.options, this.url);
59
+ return Promise.resolve(this);
60
+ }
61
+ get url() {
62
+ const server = this.bun?.server;
63
+ if (!server) return;
64
+ const address = server.address;
65
+ if (address) return fmtURL(address.address, address.port, server.protocol === "https");
66
+ return server.url.href;
67
+ }
68
+ ready() {
69
+ return Promise.resolve(this);
70
+ }
71
+ close(closeAll) {
72
+ return Promise.resolve(this.bun?.server?.stop(closeAll));
73
+ }
74
+ };
75
+
76
+ //#endregion
77
+ export { FastResponse, FastURL, serve };
@@ -0,0 +1,13 @@
1
+ //#region src/_middleware.ts
2
+ function wrapFetch(server) {
3
+ const fetchHandler = server.options.fetch;
4
+ const middleware = server.options.middleware || [];
5
+ return middleware.length === 0 ? fetchHandler : (request) => callMiddleware(request, fetchHandler, middleware, 0);
6
+ }
7
+ function callMiddleware(request, fetchHandler, middleware, index) {
8
+ if (index === middleware.length) return fetchHandler(request);
9
+ return middleware[index](request, () => callMiddleware(request, fetchHandler, middleware, index + 1));
10
+ }
11
+
12
+ //#endregion
13
+ export { wrapFetch };
@@ -0,0 +1,16 @@
1
+ //#region src/_plugins.ts
2
+ const errorPlugin = (server) => {
3
+ const errorHandler = server.options.error;
4
+ if (!errorHandler) return;
5
+ server.options.middleware.unshift((_req, next) => {
6
+ try {
7
+ const res = next();
8
+ return res instanceof Promise ? res.catch((error) => errorHandler(error)) : res;
9
+ } catch (error) {
10
+ return errorHandler(error);
11
+ }
12
+ });
13
+ };
14
+
15
+ //#endregion
16
+ export { errorPlugin };
@@ -0,0 +1,13 @@
1
+ //#region src/_url.d.ts
2
+ /**
3
+ * Wrapper for URL with fast path access to `.pathname` and `.search` props.
4
+ *
5
+ * **NOTE:** It is assumed that the input URL is already ecoded and formatted from an HTTP request and contains no hash.
6
+ *
7
+ * **NOTE:** Triggering the setters or getters on other props will deoptimize to full URL parsing.
8
+ */
9
+ declare const FastURL: {
10
+ new (url: string): URL;
11
+ };
12
+ //#endregion
13
+ export { FastURL as FastURL$2 };
@@ -0,0 +1,151 @@
1
+ //#region src/_utils.node.ts
2
+ function resolvePortAndHost(opts) {
3
+ const _port = opts.port ?? globalThis.process?.env.PORT ?? 3e3;
4
+ const port = typeof _port === "number" ? _port : Number.parseInt(_port, 10);
5
+ const hostname = opts.hostname ?? globalThis.process?.env.HOST;
6
+ return {
7
+ port,
8
+ hostname
9
+ };
10
+ }
11
+ function fmtURL(host, port, secure) {
12
+ if (!host || !port) return void 0;
13
+ if (host.includes(":")) host = `[${host}]`;
14
+ return `http${secure ? "s" : ""}://${host}:${port}/`;
15
+ }
16
+ function printListening(opts, url) {
17
+ if (!url || (opts.silent ?? globalThis.process?.env?.TEST)) return;
18
+ const _url = new URL(url);
19
+ const allInterfaces = _url.hostname === "[::]" || _url.hostname === "0.0.0.0";
20
+ if (allInterfaces) {
21
+ _url.hostname = "localhost";
22
+ url = _url.href;
23
+ }
24
+ let listeningOn = `➜ Listening on:`;
25
+ let additionalInfo = allInterfaces ? " (all interfaces)" : "";
26
+ if (globalThis.process.stdout?.isTTY) {
27
+ listeningOn = `\u001B[32m${listeningOn}\u001B[0m`;
28
+ url = `\u001B[36m${url}\u001B[0m`;
29
+ additionalInfo = `\u001B[2m${additionalInfo}\u001B[0m`;
30
+ }
31
+ console.log(` ${listeningOn} ${url}${additionalInfo}`);
32
+ }
33
+ function resolveTLSOptions(opts) {
34
+ if (!opts.tls || opts.protocol === "http") return;
35
+ const cert = resolveCertOrKey(opts.tls.cert);
36
+ const key = resolveCertOrKey(opts.tls.key);
37
+ if (!cert && !key) {
38
+ if (opts.protocol === "https") throw new TypeError("TLS `cert` and `key` must be provided for `https` protocol.");
39
+ return;
40
+ }
41
+ if (!cert || !key) throw new TypeError("TLS `cert` and `key` must be provided together.");
42
+ return {
43
+ cert,
44
+ key,
45
+ passphrase: opts.tls.passphrase
46
+ };
47
+ }
48
+ function resolveCertOrKey(value) {
49
+ if (!value) return;
50
+ if (typeof value !== "string") throw new TypeError("TLS certificate and key must be strings in PEM format or file paths.");
51
+ if (value.startsWith("-----BEGIN ")) return value;
52
+ const { readFileSync } = process.getBuiltinModule("node:fs");
53
+ return readFileSync(value, "utf8");
54
+ }
55
+
56
+ //#endregion
57
+ //#region src/_url.ts
58
+ /**
59
+ * Wrapper for URL with fast path access to `.pathname` and `.search` props.
60
+ *
61
+ * **NOTE:** It is assumed that the input URL is already ecoded and formatted from an HTTP request and contains no hash.
62
+ *
63
+ * **NOTE:** Triggering the setters or getters on other props will deoptimize to full URL parsing.
64
+ */
65
+ const FastURL = /* @__PURE__ */ (() => {
66
+ const FastURL$1 = class URL$1 {
67
+ #originalURL;
68
+ #parsedURL;
69
+ _pathname;
70
+ _urlqindex;
71
+ _query;
72
+ _search;
73
+ constructor(url) {
74
+ this.#originalURL = url;
75
+ }
76
+ get _url() {
77
+ if (!this.#parsedURL) this.#parsedURL = new globalThis.URL(this.#originalURL);
78
+ return this.#parsedURL;
79
+ }
80
+ toString() {
81
+ return this._url.toString();
82
+ }
83
+ toJSON() {
84
+ return this.toString();
85
+ }
86
+ get pathname() {
87
+ if (this.#parsedURL) return this.#parsedURL.pathname;
88
+ if (this._pathname === void 0) {
89
+ const url = this.#originalURL;
90
+ const protoIndex = url.indexOf("://");
91
+ if (protoIndex === -1) return this._url.pathname;
92
+ const pIndex = url.indexOf(
93
+ "/",
94
+ protoIndex + 4
95
+ /* :// */
96
+ );
97
+ if (pIndex === -1) return this._url.pathname;
98
+ const qIndex = this._urlqindex = url.indexOf("?", pIndex);
99
+ this._pathname = url.slice(pIndex, qIndex === -1 ? void 0 : qIndex);
100
+ }
101
+ return this._pathname;
102
+ }
103
+ set pathname(value) {
104
+ this._pathname = void 0;
105
+ this._url.pathname = value;
106
+ }
107
+ get searchParams() {
108
+ if (this.#parsedURL) return this.#parsedURL.searchParams;
109
+ if (!this._query) this._query = new URLSearchParams(this.search);
110
+ return this._query;
111
+ }
112
+ get search() {
113
+ if (this.#parsedURL) return this.#parsedURL.search;
114
+ if (this._search === void 0) {
115
+ const qIndex = this._urlqindex;
116
+ if (qIndex === -1 || qIndex === this.#originalURL.length - 1) this._search = "";
117
+ else this._search = qIndex === void 0 ? this._url.search : this.#originalURL.slice(qIndex);
118
+ }
119
+ return this._search;
120
+ }
121
+ set search(value) {
122
+ this._search = void 0;
123
+ this._query = void 0;
124
+ this._url.search = value;
125
+ }
126
+ };
127
+ const slowProps = [
128
+ "hash",
129
+ "host",
130
+ "hostname",
131
+ "href",
132
+ "origin",
133
+ "password",
134
+ "port",
135
+ "protocol",
136
+ "username"
137
+ ];
138
+ for (const prop of slowProps) Object.defineProperty(FastURL$1.prototype, prop, {
139
+ get() {
140
+ return this._url[prop];
141
+ },
142
+ set(value) {
143
+ this._url[prop] = value;
144
+ }
145
+ });
146
+ Object.setPrototypeOf(FastURL$1, globalThis.URL);
147
+ return FastURL$1;
148
+ })();
149
+
150
+ //#endregion
151
+ export { FastURL as FastURL$1, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions };
@@ -0,0 +1,237 @@
1
+ import * as NodeHttp$1 from "node:http";
2
+ import * as NodeHttps from "node:https";
3
+ import * as NodeHttp2 from "node:http2";
4
+ import * as NodeNet from "node:net";
5
+ import * as Bun from "bun";
6
+ import * as CF from "@cloudflare/workers-types";
7
+
8
+ //#region src/types.d.ts
9
+ type MaybePromise<T> = T | Promise<T>;
10
+ /**
11
+ * Faster URL constructor with lazy access to pathname and search params (For Node, Deno, and Bun).
12
+ */
13
+ declare const FastURL: typeof globalThis.URL;
14
+ /**
15
+ * Faster Response constructor optimized for Node.js (same as Response for other runtimes).
16
+ */
17
+ declare const FastResponse: typeof globalThis.Response;
18
+ /**
19
+ * Create a new server instance.
20
+ */
21
+ declare function serve(options: ServerOptions): Server;
22
+ /**
23
+ * Web fetch compatible request handler
24
+ */
25
+ type ServerHandler = (request: ServerRequest) => MaybePromise<Response>;
26
+ type ServerMiddleware = (request: ServerRequest, next: () => Response | Promise<Response>) => Response | Promise<Response>;
27
+ type ServerPlugin = (server: Server) => void;
28
+ /**
29
+ * Server options
30
+ */
31
+ interface ServerOptions {
32
+ /**
33
+ * The fetch handler handles incoming requests.
34
+ */
35
+ fetch: ServerHandler;
36
+ /**
37
+ * Handle lifecycle errors.
38
+ *
39
+ * @note This handler will set built-in Bun and Deno error handler.
40
+ */
41
+ error?: ErrorHandler;
42
+ /**
43
+ * Server middleware handlers to run before the main fetch handler.
44
+ */
45
+ middleware?: ServerMiddleware[];
46
+ /**
47
+ * Server plugins.
48
+ */
49
+ plugins?: ServerPlugin[];
50
+ /**
51
+ * If set to `true`, server will not start listening automatically.
52
+ */
53
+ manual?: boolean;
54
+ /**
55
+ * The port server should be listening to.
56
+ *
57
+ * Default is read from `PORT` environment variable or will be `3000`.
58
+ *
59
+ * **Tip:** You can set the port to `0` to use a random port.
60
+ */
61
+ port?: string | number;
62
+ /**
63
+ * The hostname (IP or resolvable host) server listener should bound to.
64
+ *
65
+ * When not provided, server with listen to all network interfaces by default.
66
+ *
67
+ * **Important:** If you are running a server that is not expected to be exposed to the network, use `hostname: "localhost"`.
68
+ */
69
+ hostname?: string;
70
+ /**
71
+ * Enabling this option allows multiple processes to bind to the same port, which is useful for load balancing.
72
+ *
73
+ * **Note:** Despite Node.js built-in behavior that has `exclusive` flag (opposite of `reusePort`) enabled by default, srvx uses non-exclusive mode for consistency.
74
+ */
75
+ reusePort?: boolean;
76
+ /**
77
+ * The protocol to use for the server.
78
+ *
79
+ * Possible values are `http` and `https`.
80
+ *
81
+ * If `protocol` is not set, Server will use `http` as the default protocol or `https` if both `tls.cert` and `tls.key` options are provided.
82
+ */
83
+ protocol?: "http" | "https";
84
+ /**
85
+ * If set to `true`, server will not print the listening address.
86
+ */
87
+ silent?: boolean;
88
+ /**
89
+ * TLS server options.
90
+ */
91
+ tls?: {
92
+ /**
93
+ * File path or inlined TLS certificate in PEM format (required).
94
+ */
95
+ cert?: string;
96
+ /**
97
+ * File path or inlined TLS private key in PEM format (required).
98
+ */
99
+ key?: string;
100
+ /**
101
+ * Passphrase for the private key (optional).
102
+ */
103
+ passphrase?: string;
104
+ };
105
+ /**
106
+ * Node.js server options.
107
+ */
108
+ node?: (NodeHttp$1.ServerOptions | NodeHttps.ServerOptions | NodeHttp2.ServerOptions) & NodeNet.ListenOptions & {
109
+ http2?: boolean;
110
+ };
111
+ /**
112
+ * Bun server options
113
+ *
114
+ * @docs https://bun.sh/docs/api/http
115
+ */
116
+ bun?: Omit<Bun.ServeOptions | Bun.TLSServeOptions, "fetch">;
117
+ /**
118
+ * Deno server options
119
+ *
120
+ * @docs https://docs.deno.com/api/deno/~/Deno.serve
121
+ */
122
+ deno?: Deno.ServeOptions;
123
+ /**
124
+ * Service worker options
125
+ */
126
+ serviceWorker?: {
127
+ /**
128
+ * The path to the service worker file to be registered.
129
+ */
130
+ url?: string;
131
+ /**
132
+ * The scope of the service worker.
133
+ *
134
+ */
135
+ scope?: string;
136
+ };
137
+ }
138
+ interface Server<Handler = ServerHandler> {
139
+ /**
140
+ * Current runtime name
141
+ */
142
+ readonly runtime: "node" | "deno" | "bun" | "cloudflare" | "service-worker" | "generic";
143
+ /**
144
+ * Server options
145
+ */
146
+ readonly options: ServerOptions & {
147
+ middleware: ServerMiddleware[];
148
+ };
149
+ /**
150
+ * Server URL address.
151
+ */
152
+ readonly url?: string;
153
+ /**
154
+ * Node.js context.
155
+ */
156
+ readonly node?: {
157
+ server?: NodeHttp$1.Server | NodeHttp2.Http2Server;
158
+ handler: (req: NodeServerRequest, res: NodeServerResponse) => void | Promise<void>;
159
+ };
160
+ /**
161
+ * Bun context.
162
+ */
163
+ readonly bun?: {
164
+ server?: Bun.Server;
165
+ };
166
+ /**
167
+ * Deno context.
168
+ */
169
+ readonly deno?: {
170
+ server?: Deno.HttpServer;
171
+ };
172
+ /**
173
+ * Server fetch handler
174
+ */
175
+ readonly fetch: Handler;
176
+ /**
177
+ * Returns a promise that resolves when the server is ready.
178
+ */
179
+ ready(): Promise<Server<Handler>>;
180
+ /**
181
+ * Stop listening to prevent new connections from being accepted.
182
+ *
183
+ * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
184
+ *
185
+ * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
186
+ * @default false
187
+ */
188
+ close(closeActiveConnections?: boolean): Promise<void>;
189
+ }
190
+ interface ServerRuntimeContext {
191
+ name: "node" | "deno" | "bun" | "cloudflare" | (string & {});
192
+ /**
193
+ * Underlying Node.js server request info.
194
+ */
195
+ node?: {
196
+ req: NodeServerRequest;
197
+ res?: NodeServerResponse;
198
+ };
199
+ /**
200
+ * Underlying Deno server request info.
201
+ */
202
+ deno?: {
203
+ info: Deno.ServeHandlerInfo<Deno.NetAddr>;
204
+ };
205
+ /**
206
+ * Underlying Bun server request context.
207
+ */
208
+ bun?: {
209
+ server: Bun.Server;
210
+ };
211
+ /**
212
+ * Underlying Cloudflare request context.
213
+ */
214
+ cloudflare?: {
215
+ env: unknown;
216
+ context: CF.ExecutionContext;
217
+ };
218
+ }
219
+ interface ServerRequest extends Request {
220
+ /**
221
+ * Runtime specific request context.
222
+ */
223
+ runtime?: ServerRuntimeContext;
224
+ /**
225
+ * IP address of the client.
226
+ */
227
+ ip?: string | undefined;
228
+ }
229
+ type FetchHandler = (request: Request) => Response | Promise<Response>;
230
+ type ErrorHandler = (error: unknown) => Response | Promise<Response>;
231
+ type BunFetchHandler = (request: Request, server?: Bun.Server) => Response | Promise<Response>;
232
+ type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
233
+ type NodeServerRequest = NodeHttp$1.IncomingMessage | NodeHttp2.Http2ServerRequest;
234
+ type NodeServerResponse = NodeHttp$1.ServerResponse | NodeHttp2.Http2ServerResponse;
235
+ type NodeHttpHandler = (req: NodeServerRequest, res: NodeServerResponse) => void | Promise<void>;
236
+ type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler; //#endregion
237
+ export { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FastResponse, FastURL, FetchHandler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerRequest, ServerRuntimeContext, serve };
@@ -0,0 +1,10 @@
1
+ import { Server, ServerOptions } from "./chunks/types-egi3HKdd.mjs";
2
+ import * as CF from "@cloudflare/workers-types";
3
+
4
+ //#region src/adapters/cloudflare.d.ts
5
+ declare const FastURL: typeof globalThis.URL;
6
+ declare const FastResponse: typeof globalThis.Response;
7
+ declare function serve(options: ServerOptions): Server<CF.ExportedHandlerFetchHandler>;
8
+
9
+ //#endregion
10
+ export { FastResponse, FastURL, serve };
@@ -0,0 +1,53 @@
1
+ import { wrapFetch } from "./chunks/_middleware-DSFIayTI.mjs";
2
+ import { errorPlugin } from "./chunks/_plugins-BTotWVHV.mjs";
3
+
4
+ //#region src/adapters/cloudflare.ts
5
+ const FastURL = URL;
6
+ const FastResponse = Response;
7
+ function serve(options) {
8
+ return new CloudflareServer(options);
9
+ }
10
+ var CloudflareServer = class {
11
+ runtime = "cloudflare";
12
+ options;
13
+ serveOptions;
14
+ fetch;
15
+ constructor(options) {
16
+ this.options = {
17
+ ...options,
18
+ middleware: [...options.middleware || []]
19
+ };
20
+ for (const plugin of options.plugins || []) plugin(this);
21
+ errorPlugin(this);
22
+ const fetchHandler = wrapFetch(this);
23
+ this.fetch = (request, env, context) => {
24
+ Object.defineProperties(request, { runtime: {
25
+ enumerable: true,
26
+ value: {
27
+ runtime: "cloudflare",
28
+ cloudflare: {
29
+ env,
30
+ context
31
+ }
32
+ }
33
+ } });
34
+ return fetchHandler(request);
35
+ };
36
+ this.serveOptions = { fetch: this.fetch };
37
+ if (!options.manual) this.serve();
38
+ }
39
+ serve() {
40
+ addEventListener("fetch", (event) => {
41
+ event.respondWith(this.fetch(event.request, {}, event));
42
+ });
43
+ }
44
+ ready() {
45
+ return Promise.resolve().then(() => this);
46
+ }
47
+ close() {
48
+ return Promise.resolve();
49
+ }
50
+ };
51
+
52
+ //#endregion
53
+ export { FastResponse, FastURL, serve };
@@ -0,0 +1,22 @@
1
+ import { DenoFetchHandler, Server, ServerOptions } from "./chunks/types-egi3HKdd.mjs";
2
+ import { FastURL$2 as FastURL } from "./chunks/_url-BwTFuoNW.mjs";
3
+
4
+ //#region src/adapters/deno.d.ts
5
+ declare const FastResponse: typeof globalThis.Response;
6
+ declare function serve(options: ServerOptions): DenoServer;
7
+ declare class DenoServer implements Server<DenoFetchHandler> {
8
+ #private;
9
+ readonly runtime = "deno";
10
+ readonly options: Server["options"];
11
+ readonly deno: Server["deno"];
12
+ readonly serveOptions: Deno.ServeTcpOptions | (Deno.ServeTcpOptions & Deno.TlsCertifiedKeyPem);
13
+ readonly fetch: DenoFetchHandler;
14
+ constructor(options: ServerOptions);
15
+ serve(): Promise<this>;
16
+ get url(): string | undefined;
17
+ ready(): Promise<Server>;
18
+ close(): Promise<void | undefined>;
19
+ }
20
+
21
+ //#endregion
22
+ export { FastResponse, FastURL, serve };