srvx-nightly 0.11.21 → 0.11.22-20260711-160642-e093402

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.
@@ -0,0 +1,58 @@
1
+ function isTrustedProxy(trustProxy, remoteAddress) {
2
+ if (trustProxy === void 0 || trustProxy === false) return false;
3
+ if (trustProxy === true) return true;
4
+ if (trustProxy === "loopback") return isLoopbackAddress(remoteAddress);
5
+ if (remoteAddress === void 0) return false;
6
+ if (trustProxy.includes(remoteAddress)) return true;
7
+ const mapped = ipv4FromMapped(remoteAddress);
8
+ return mapped !== void 0 && trustProxy.includes(mapped);
9
+ }
10
+ function ipv4FromMapped(address) {
11
+ return address.startsWith("::ffff:") && address.includes(".") ? address.slice(7) : void 0;
12
+ }
13
+ function isLoopbackAddress(address) {
14
+ return !!address && (address === "::1" || address.startsWith("127.") || address.startsWith("::ffff:127."));
15
+ }
16
+ const HOST_RE = /^(\[(?:[A-Fa-f0-9:.]+)\]|(?:[A-Za-z0-9_-]+\.)*[A-Za-z0-9_-]+|(?:\d{1,3}\.){3}\d{1,3})(:\d{1,5})?$/;
17
+ function forwardedHostHasPort(host) {
18
+ const bracket = host.lastIndexOf("]");
19
+ return bracket === -1 ? host.includes(":") : host.indexOf(":", bracket) !== -1;
20
+ }
21
+ function firstForwardedValue(value) {
22
+ if (!value) return;
23
+ return (Array.isArray(value) ? value[0] : value).split(",")[0].trim() || void 0;
24
+ }
25
+ const trustProxyPlugin = (server) => {
26
+ const trustProxy = server.options.trustProxy;
27
+ if (trustProxy === void 0 || trustProxy === false) return;
28
+ server.options.middleware.unshift((request, next) => {
29
+ applyTrustedProxy(request, trustProxy);
30
+ return next();
31
+ });
32
+ };
33
+ function applyTrustedProxy(request, trustProxy) {
34
+ if (!isTrustedProxy(trustProxy, request.ip)) return;
35
+ const headers = request.headers;
36
+ const forwardedProto = firstForwardedValue(headers.get("x-forwarded-proto"));
37
+ const forwardedHost = firstForwardedValue(headers.get("x-forwarded-host"));
38
+ if (forwardedProto || forwardedHost) {
39
+ const url = new URL(request.url);
40
+ if (forwardedProto === "https" || forwardedProto === "http") url.protocol = `${forwardedProto}:`;
41
+ if (forwardedHost && HOST_RE.test(forwardedHost)) {
42
+ url.host = forwardedHost;
43
+ if (url.port && !forwardedHostHasPort(forwardedHost)) url.port = "";
44
+ }
45
+ Object.defineProperty(request, "url", {
46
+ value: url.href,
47
+ enumerable: true,
48
+ configurable: true
49
+ });
50
+ }
51
+ const forwardedFor = firstForwardedValue(headers.get("x-forwarded-for"));
52
+ if (forwardedFor) Object.defineProperty(request, "ip", {
53
+ value: forwardedFor,
54
+ enumerable: true,
55
+ configurable: true
56
+ });
57
+ }
58
+ export { HOST_RE, firstForwardedValue, isTrustedProxy, trustProxyPlugin };
@@ -72,4 +72,4 @@ function createWaitUntil() {
72
72
  }
73
73
  };
74
74
  }
75
- export { createWaitUntil, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions, toNativeResponse };
75
+ export { createWaitUntil, fmtURL, printListening, resolveCertOrKey, resolvePortAndHost, resolveTLSOptions, toNativeResponse };
@@ -0,0 +1,363 @@
1
+ import * as NodeHttp from "node:http";
2
+ import * as NodeHttps from "node:https";
3
+ import * as NodeHttp2 from "node:http2";
4
+ import * as AWS from "aws-lambda";
5
+ import * as NodeNet from "node:net";
6
+ import * as Bun from "bun";
7
+ import * as CF from "@cloudflare/workers-types";
8
+ /**
9
+ * Controls whether `X-Forwarded-*` headers (proto, host, for, and the HTTP/2
10
+ * `:scheme` pseudo-header) are trusted when deriving request metadata.
11
+ *
12
+ * These headers are set by the client on the wire, so they can only be trusted
13
+ * when a proxy you control sits in front and overwrites them. See
14
+ * {@link ServerOptions.trustProxy}.
15
+ *
16
+ * - `false` (default): never trust forwarded headers; derive protocol, host
17
+ * and client IP from the real transport only.
18
+ * - `true`: always trust forwarded headers.
19
+ * - `"loopback"`: trust only when the immediate peer is a loopback address
20
+ * (`127.0.0.0/8` or `::1`), i.e. a proxy running on the same host.
21
+ * - `string[]`: trust only when the immediate peer address is in the allowlist.
22
+ */
23
+ type TrustProxyOption = boolean | "loopback" | string[];
24
+ type MaybePromise<T> = T | Promise<T>;
25
+ type IsAny<T> = Equal<T, any> extends true ? true : false;
26
+ type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
27
+ /**
28
+ * Faster URL constructor with lazy access to pathname and search params (For Node, Deno, and Bun).
29
+ */
30
+ declare const FastURL: typeof globalThis.URL;
31
+ /**
32
+ * Faster Response constructor optimized for Node.js (same as Response for other runtimes).
33
+ */
34
+ declare const FastResponse: typeof globalThis.Response;
35
+ /**
36
+ * Create a new server instance.
37
+ */
38
+ declare function serve(options: ServerOptions): Server;
39
+ /**
40
+ * Web fetch compatible request handler
41
+ */
42
+ type ServerHandler = (request: ServerRequest) => MaybePromise<Response>;
43
+ type ServerMiddleware = (request: ServerRequest, next: () => Response | Promise<Response>) => Response | Promise<Response>;
44
+ type ServerPlugin = (server: Server) => void;
45
+ /**
46
+ * Server options
47
+ */
48
+ interface ServerOptions {
49
+ /**
50
+ * The fetch handler handles incoming requests.
51
+ */
52
+ fetch: ServerHandler;
53
+ /**
54
+ * Handle lifecycle errors.
55
+ *
56
+ * @note This handler will set built-in Bun and Deno error handler.
57
+ */
58
+ error?: ErrorHandler;
59
+ /**
60
+ * Server middleware handlers to run before the main fetch handler.
61
+ */
62
+ middleware?: ServerMiddleware[];
63
+ /**
64
+ * Server plugins.
65
+ */
66
+ plugins?: ServerPlugin[];
67
+ /**
68
+ * If set to `true`, server will not start listening automatically.
69
+ */
70
+ manual?: boolean;
71
+ /**
72
+ * The port server should be listening to.
73
+ *
74
+ * Default is read from `PORT` environment variable or will be `3000`.
75
+ *
76
+ * **Tip:** You can set the port to `0` to use a random port.
77
+ */
78
+ port?: string | number;
79
+ /**
80
+ * The hostname (IP or resolvable host) server listener should bound to.
81
+ *
82
+ * When not provided, server with listen to all network interfaces by default.
83
+ *
84
+ * **Important:** If you are running a server that is not expected to be exposed to the network, use `hostname: "localhost"`.
85
+ */
86
+ hostname?: string;
87
+ /**
88
+ * Enabling this option allows multiple processes to bind to the same port, which is useful for load balancing.
89
+ *
90
+ * **Note:** Despite Node.js built-in behavior that has `exclusive` flag (opposite of `reusePort`) enabled by default, srvx uses non-exclusive mode for consistency.
91
+ */
92
+ reusePort?: boolean;
93
+ /**
94
+ * The protocol to use for the server.
95
+ *
96
+ * Possible values are `http` and `https`.
97
+ *
98
+ * 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.
99
+ */
100
+ protocol?: "http" | "https";
101
+ /**
102
+ * If set to `true`, server will not print the listening address.
103
+ */
104
+ silent?: boolean;
105
+ /**
106
+ * Graceful shutdown on SIGINT and SIGTERM signals.
107
+ *
108
+ * Supported for Node.js, Deno and Bun runtimes.
109
+ *
110
+ * @default true (disabled in test and ci environments)
111
+ */
112
+ gracefulShutdown?: boolean | {
113
+ gracefulTimeout?: number;
114
+ forceTimeout?: number;
115
+ };
116
+ /**
117
+ * Maximum allowed size (in bytes) for the request body.
118
+ *
119
+ * As the body is read, its accumulated length is tracked and, once it exceeds
120
+ * this limit, reading is aborted and rejects with a `413`-style error (the error
121
+ * has `statusCode: 413`, `status: 413` and `code: "ERR_BODY_TOO_LARGE"`) so a
122
+ * handler can map it to an HTTP 413 (Payload Too Large) response.
123
+ *
124
+ * The limit covers the buffered reads (`request.text()` / `request.json()`) as
125
+ * well as the streamed body (`request.body`, and therefore `arrayBuffer()` /
126
+ * `blob()` / `bytes()` / `formData()`).
127
+ *
128
+ * Runtime support:
129
+ * - **Node**: enforced by srvx (body stream is size-limited).
130
+ * - **Bun**: mapped to Bun's native `maxRequestBodySize` (413 before the handler).
131
+ * - **Deno**: enforced by srvx (request body stream is size-limited).
132
+ *
133
+ * @default undefined (no limit)
134
+ */
135
+ maxRequestBodySize?: number;
136
+ /**
137
+ * Whether to trust `X-Forwarded-*` headers (`X-Forwarded-Proto`,
138
+ * `X-Forwarded-Host`, `X-Forwarded-For`, and the HTTP/2 `:scheme`) when
139
+ * deriving `request.url` and `request.ip`.
140
+ *
141
+ * Any client can send `X-Forwarded-Proto: https`, `X-Forwarded-Host` or
142
+ * `X-Forwarded-For`, so trusting them lets a request masquerade as `https:`,
143
+ * forge its host, or spoof its client IP. Only enable this when a proxy you
144
+ * control sits in front and overwrites the headers.
145
+ *
146
+ * - `false` (default): ignore the headers; use the real connection protocol,
147
+ * the on-the-wire `Host` header and the socket peer address.
148
+ * - `true`: always trust the headers.
149
+ * - `"loopback"`: trust them only when the proxy connects from a loopback
150
+ * address (`127.0.0.0/8` or `::1`).
151
+ * - `string[]`: trust them only when the proxy's address is in the list.
152
+ *
153
+ * Applies to the Node, AWS Lambda, Bun and Deno adapters.
154
+ *
155
+ * @default false
156
+ */
157
+ trustProxy?: TrustProxyOption;
158
+ /**
159
+ * TLS server options.
160
+ */
161
+ tls?: {
162
+ /**
163
+ * File path or inlined TLS certificate in PEM format (required).
164
+ */
165
+ cert?: string;
166
+ /**
167
+ * File path or inlined TLS private key in PEM format (required).
168
+ */
169
+ key?: string;
170
+ /**
171
+ * Passphrase for the private key (optional).
172
+ */
173
+ passphrase?: string;
174
+ };
175
+ /**
176
+ * Node.js server options.
177
+ */
178
+ node?: (NodeHttp.ServerOptions | NodeHttps.ServerOptions | NodeHttp2.ServerOptions) & NodeNet.ListenOptions & {
179
+ http2?: boolean;
180
+ };
181
+ /**
182
+ * Bun server options
183
+ *
184
+ * @docs https://bun.sh/docs/api/http
185
+ */
186
+ bun?: Omit<Bun.Serve.Options<any>, "fetch">;
187
+ /**
188
+ * Deno server options
189
+ *
190
+ * @docs https://docs.deno.com/api/deno/~/Deno.serve
191
+ */
192
+ deno?: Deno.ServeOptions;
193
+ /**
194
+ * Service worker options
195
+ */
196
+ serviceWorker?: {
197
+ /**
198
+ * The path to the service worker file to be registered.
199
+ */
200
+ url?: string;
201
+ /**
202
+ * The scope of the service worker.
203
+ *
204
+ */
205
+ scope?: string;
206
+ };
207
+ }
208
+ interface Server<Handler = ServerHandler> {
209
+ /**
210
+ * Current runtime name
211
+ */
212
+ readonly runtime: "node" | "deno" | "bun" | "bunny" | "cloudflare" | "service-worker" | "aws-lambda" | "generic";
213
+ /**
214
+ * Server options
215
+ */
216
+ readonly options: ServerOptions & {
217
+ middleware: ServerMiddleware[];
218
+ };
219
+ /**
220
+ * Server URL address.
221
+ */
222
+ readonly url?: string;
223
+ /**
224
+ * Node.js context.
225
+ */
226
+ readonly node?: {
227
+ server?: NodeHttp.Server | NodeHttp2.Http2Server;
228
+ handler: (req: NodeServerRequest, res: NodeServerResponse) => void | Promise<void>;
229
+ };
230
+ /**
231
+ * Bun context.
232
+ */
233
+ readonly bun?: {
234
+ server?: Bun.Server<any>;
235
+ };
236
+ /**
237
+ * Deno context.
238
+ */
239
+ readonly deno?: {
240
+ server?: Deno.HttpServer;
241
+ };
242
+ /**
243
+ * Server fetch handler
244
+ */
245
+ readonly fetch: Handler;
246
+ /**
247
+ * Start listening for incoming requests.
248
+ * When `manual` option is enabled, this method needs to be called explicitly to begin accepting connections.
249
+ */
250
+ serve(): void | Promise<Server<Handler>>;
251
+ /**
252
+ * Returns a promise that resolves when the server is ready.
253
+ */
254
+ ready(): Promise<Server<Handler>>;
255
+ /**
256
+ * Register a background task that the server should await before closing.
257
+ *
258
+ * Same as `request.waitUntil` but available at the server level for use outside of request handlers.
259
+ */
260
+ readonly waitUntil?: (promise: Promise<unknown>) => void;
261
+ /**
262
+ * Stop listening to prevent new connections from being accepted.
263
+ *
264
+ * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
265
+ *
266
+ * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
267
+ * @default false
268
+ */
269
+ close(closeActiveConnections?: boolean): Promise<void>;
270
+ }
271
+ interface ServerRuntimeContext {
272
+ name: "node" | "deno" | "bun" | "bunny" | "cloudflare" | "aws-lambda" | (string & {});
273
+ /**
274
+ * Underlying Node.js server request info.
275
+ */
276
+ node?: {
277
+ req: NodeServerRequest;
278
+ res?: NodeServerResponse;
279
+ };
280
+ /**
281
+ * Underlying Deno server request info.
282
+ */
283
+ deno?: {
284
+ info: Deno.ServeHandlerInfo<Deno.NetAddr>;
285
+ };
286
+ /**
287
+ * Underlying Bun server request context.
288
+ */
289
+ bun?: {
290
+ server: Bun.Server<any>;
291
+ };
292
+ /**
293
+ * Underlying Cloudflare request context.
294
+ */
295
+ cloudflare?: {
296
+ context: CF.ExecutionContext;
297
+ env: IsAny<typeof import("cloudflare:workers")> extends true ? Record<string, unknown> : typeof import("cloudflare:workers").env;
298
+ };
299
+ awsLambda?: {
300
+ context: AWS.Context;
301
+ event: AWS.APIGatewayProxyEvent | AWS.APIGatewayProxyEventV2;
302
+ };
303
+ serviceWorker?: {
304
+ event: FetchEvent;
305
+ };
306
+ netlify?: {
307
+ context: any;
308
+ };
309
+ stormkit?: {
310
+ event: any;
311
+ context: any;
312
+ };
313
+ vercel?: {
314
+ context: {
315
+ waitUntil?: (promise: Promise<any>) => void;
316
+ };
317
+ };
318
+ }
319
+ interface ServerRequestContext {
320
+ [key: string]: unknown;
321
+ }
322
+ interface ServerRequest extends Request {
323
+ /**
324
+ * Access to Node.js native instance of request.
325
+ *
326
+ * See https://srvx.h3.dev/guide/node#noderequest
327
+ */
328
+ _request?: Request;
329
+ /**
330
+ * Access to the parsed URL
331
+ */
332
+ _url?: URL;
333
+ /**
334
+ * Runtime specific request context.
335
+ */
336
+ runtime?: ServerRuntimeContext;
337
+ /**
338
+ * IP address of the client.
339
+ */
340
+ ip?: string | undefined;
341
+ /**
342
+ * Arbitrary context related to the request.
343
+ */
344
+ context?: ServerRequestContext;
345
+ /**
346
+ * Tell the runtime about an ongoing operation that shouldn't close until the promise resolves.
347
+ */
348
+ waitUntil?: (promise: Promise<unknown>) => void | Promise<void>;
349
+ }
350
+ type FetchHandler = (request: Request) => Response | Promise<Response>;
351
+ type ErrorHandler = (error: unknown) => Response | Promise<Response>;
352
+ type BunFetchHandler = (request: Request, server?: Bun.Server<any>) => Response | Promise<Response>;
353
+ type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
354
+ type NodeServerRequest = NodeHttp.IncomingMessage | NodeHttp2.Http2ServerRequest;
355
+ type NodeServerResponse = NodeHttp.ServerResponse | NodeHttp2.Http2ServerResponse;
356
+ type NodeHttp1Handler = (req: NodeHttp.IncomingMessage, res: NodeHttp.ServerResponse) => void | Promise<void>;
357
+ type NodeHttp2Handler = (req: NodeHttp2.Http2ServerRequest, res: NodeHttp2.Http2ServerResponse) => void | Promise<void>;
358
+ type NodeHttpHandler = NodeHttp1Handler | NodeHttp2Handler;
359
+ type NodeHTTP1Middleware = (req: NodeHttp.IncomingMessage, res: NodeHttp.ServerResponse, next: (error?: Error) => void) => unknown | Promise<unknown>;
360
+ type NodeHTTP2Middleware = (req: NodeHttp2.Http2ServerRequest, res: NodeHttp2.Http2ServerResponse, next: (error?: Error) => void) => unknown | Promise<unknown>;
361
+ type NodeHTTPMiddleware = NodeHTTP1Middleware | NodeHTTP2Middleware;
362
+ type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
363
+ export { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FastResponse, FastURL, FetchHandler, NodeHTTP1Middleware, NodeHTTP2Middleware, NodeHTTPMiddleware, NodeHttp1Handler, NodeHttp2Handler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerRequest, ServerRequestContext, ServerRuntimeContext, TrustProxyOption, serve };
@@ -1,4 +1,4 @@
1
- import { FetchHandler, ServerOptions } from "../types.mjs";
1
+ import { FetchHandler, ServerOptions, TrustProxyOption } from "../_chunks/types.mjs";
2
2
  import * as AWS from "aws-lambda";
3
3
  type AWSLambdaResponseStream = NodeJS.WritableStream & {
4
4
  setContentType(contentType: string): void;
@@ -8,7 +8,7 @@ type AwsLambdaEvent = AWS.APIGatewayProxyEvent | AWS.APIGatewayProxyEventV2;
8
8
  type AWSLambdaHandler = (event: AwsLambdaEvent, context: AWS.Context) => MaybePromise<AWS.APIGatewayProxyResult | AWS.APIGatewayProxyResultV2>;
9
9
  type AWSLambdaStreamingHandler = (event: AwsLambdaEvent, responseStream: AWSLambdaResponseStream, context: AWS.Context) => MaybePromise<void>;
10
10
  declare function toLambdaHandler(options: ServerOptions): AWSLambdaHandler;
11
- declare function handleLambdaEvent(fetchHandler: FetchHandler, event: AwsLambdaEvent, context: AWS.Context): Promise<AWS.APIGatewayProxyResult | AWS.APIGatewayProxyResultV2>;
12
- declare function handleLambdaEventWithStream(fetchHandler: FetchHandler, event: AwsLambdaEvent, responseStream: AWSLambdaResponseStream, context: AWS.Context): Promise<void>;
11
+ declare function handleLambdaEvent(fetchHandler: FetchHandler, event: AwsLambdaEvent, context: AWS.Context, trustProxy?: TrustProxyOption): Promise<AWS.APIGatewayProxyResult | AWS.APIGatewayProxyResultV2>;
12
+ declare function handleLambdaEventWithStream(fetchHandler: FetchHandler, event: AwsLambdaEvent, responseStream: AWSLambdaResponseStream, context: AWS.Context, trustProxy?: TrustProxyOption): Promise<void>;
13
13
  declare function invokeLambdaHandler(handler: AWSLambdaHandler, request: Request): Promise<Response>;
14
14
  export { AWSLambdaHandler, type AWSLambdaResponseStream, AWSLambdaStreamingHandler, AwsLambdaEvent, handleLambdaEvent, handleLambdaEventWithStream, invokeLambdaHandler, toLambdaHandler };
@@ -1,6 +1,9 @@
1
1
  import { errorPlugin, wrapFetch } from "../_chunks/_plugins.mjs";
2
- function awsRequest(event, context) {
3
- const req = new Request(awsEventURL(event), {
2
+ import { firstForwardedValue, isTrustedProxy } from "../_chunks/_trust-proxy.mjs";
3
+ function awsRequest(event, context, trustProxy) {
4
+ const sourceIp = awsEventIP(event);
5
+ const trusted = isTrustedProxy(trustProxy, sourceIp);
6
+ const req = new Request(awsEventURL(event, trusted), {
4
7
  method: awsEventMethod(event),
5
8
  headers: awsEventHeaders(event),
6
9
  body: awsEventBody(event)
@@ -12,7 +15,7 @@ function awsRequest(event, context) {
12
15
  context
13
16
  }
14
17
  };
15
- req.ip = awsEventIP(event);
18
+ req.ip = awsEventClientIP(event, sourceIp, trusted);
16
19
  return req;
17
20
  }
18
21
  function awsEventMethod(event) {
@@ -21,11 +24,18 @@ function awsEventMethod(event) {
21
24
  function awsEventIP(event) {
22
25
  return event.requestContext?.http?.sourceIp || event.requestContext?.identity?.sourceIp;
23
26
  }
24
- function awsEventURL(event) {
25
- const hostname = event.headers.host || event.headers.Host || event.requestContext?.domainName || ".";
27
+ function awsEventClientIP(event, sourceIp, trusted) {
28
+ if (trusted) {
29
+ const forwarded = firstForwardedValue(event.headers["X-Forwarded-For"] || event.headers["x-forwarded-for"]);
30
+ if (forwarded) return forwarded;
31
+ }
32
+ return sourceIp;
33
+ }
34
+ function awsEventURL(event, trusted) {
26
35
  const path = event.path || event.rawPath;
27
36
  const query = awsEventQuery(event);
28
- const protocol = (event.headers["X-Forwarded-Proto"] || event.headers["x-forwarded-proto"]) === "http" ? "http" : "https";
37
+ const hostname = (trusted ? firstForwardedValue(event.headers["X-Forwarded-Host"] || event.headers["x-forwarded-host"]) : void 0) || event.headers.host || event.headers.Host || event.requestContext?.domainName || ".";
38
+ const protocol = (trusted ? firstForwardedValue(event.headers["X-Forwarded-Proto"] || event.headers["x-forwarded-proto"]) : void 0) === "http" ? "http" : "https";
29
39
  return new URL(`${path}${query ? `?${query}` : ""}`, `${protocol}://${hostname}`);
30
40
  }
31
41
  function awsEventQuery(event) {
@@ -253,16 +263,16 @@ function toLambdaHandler(options) {
253
263
  const server = new AWSLambdaServer(options);
254
264
  return (event, context) => server.fetch(event, context);
255
265
  }
256
- async function handleLambdaEvent(fetchHandler, event, context) {
257
- const response = await fetchHandler(awsRequest(event, context));
266
+ async function handleLambdaEvent(fetchHandler, event, context, trustProxy) {
267
+ const response = await fetchHandler(awsRequest(event, context, trustProxy));
258
268
  return {
259
269
  statusCode: response.status,
260
270
  ...awsResponseHeaders(response, event),
261
271
  ...await awsResponseBody(response)
262
272
  };
263
273
  }
264
- async function handleLambdaEventWithStream(fetchHandler, event, responseStream, context) {
265
- await awsStreamResponse(await fetchHandler(awsRequest(event, context)), responseStream, event);
274
+ async function handleLambdaEventWithStream(fetchHandler, event, responseStream, context, trustProxy) {
275
+ await awsStreamResponse(await fetchHandler(awsRequest(event, context, trustProxy)), responseStream, event);
266
276
  }
267
277
  async function invokeLambdaHandler(handler, request) {
268
278
  return awsResultToResponse(await handler(await requestToAwsEvent(request), createMockContext()));
@@ -279,7 +289,7 @@ var AWSLambdaServer = class {
279
289
  for (const plugin of options.plugins || []) plugin(this);
280
290
  errorPlugin(this);
281
291
  const fetchHandler = wrapFetch(this);
282
- this.fetch = (event, context) => handleLambdaEvent(fetchHandler, event, context);
292
+ this.fetch = (event, context) => handleLambdaEvent(fetchHandler, event, context, this.options.trustProxy);
283
293
  }
284
294
  serve() {}
285
295
  ready() {
@@ -1,4 +1,4 @@
1
- import { BunFetchHandler, Server, ServerOptions } from "../types.mjs";
1
+ import { BunFetchHandler, Server, ServerOptions } from "../_chunks/types.mjs";
2
2
  import { FastURL } from "../_chunks/_url.mjs";
3
3
  import * as bun from "bun";
4
4
  declare const FastResponse: typeof globalThis.Response;
@@ -1,6 +1,7 @@
1
1
  import { FastURL } from "../_chunks/_url.mjs";
2
2
  import { createWaitUntil, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions, toNativeResponse } from "../_chunks/_utils2.mjs";
3
3
  import { gracefulShutdownPlugin, wrapFetch } from "../_chunks/_plugins.mjs";
4
+ import { trustProxyPlugin } from "../_chunks/_trust-proxy.mjs";
4
5
  const FastResponse = Response;
5
6
  function serve(options) {
6
7
  return new BunServer(options);
@@ -19,6 +20,7 @@ var BunServer = class {
19
20
  middleware: [...options.middleware || []]
20
21
  };
21
22
  for (const plugin of options.plugins || []) plugin(this);
23
+ trustProxyPlugin(this);
22
24
  gracefulShutdownPlugin(this);
23
25
  const fetchHandler = wrapFetch(this);
24
26
  const loader = globalThis.__srvxLoader__;
@@ -41,6 +43,7 @@ var BunServer = class {
41
43
  },
42
44
  ip: {
43
45
  enumerable: true,
46
+ configurable: true,
44
47
  get() {
45
48
  return server?.requestIP(request)?.address;
46
49
  }
@@ -1,4 +1,4 @@
1
- import { Server, ServerOptions } from "../types.mjs";
1
+ import { Server, ServerOptions } from "../_chunks/types.mjs";
2
2
  declare const FastURL: typeof globalThis.URL;
3
3
  declare const FastResponse: typeof globalThis.Response;
4
4
  declare function serve(options: ServerOptions): Server;
@@ -1,4 +1,4 @@
1
- import { Server, ServerOptions } from "../types.mjs";
1
+ import { Server, ServerOptions } from "../_chunks/types.mjs";
2
2
  import * as CF from "@cloudflare/workers-types";
3
3
  declare const FastURL: typeof globalThis.URL;
4
4
  declare const FastResponse: typeof globalThis.Response;
@@ -1,4 +1,4 @@
1
- import { DenoFetchHandler, Server, ServerOptions } from "../types.mjs";
1
+ import { DenoFetchHandler, Server, ServerOptions } from "../_chunks/types.mjs";
2
2
  import { FastURL } from "../_chunks/_url.mjs";
3
3
  declare const FastResponse: typeof globalThis.Response;
4
4
  declare function serve(options: ServerOptions): DenoServer;
@@ -1,6 +1,7 @@
1
1
  import { FastURL } from "../_chunks/_url.mjs";
2
2
  import { createWaitUntil, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions, toNativeResponse } from "../_chunks/_utils2.mjs";
3
3
  import { gracefulShutdownPlugin, wrapFetch } from "../_chunks/_plugins.mjs";
4
+ import { trustProxyPlugin } from "../_chunks/_trust-proxy.mjs";
4
5
  import { limitRequestBody } from "../_chunks/_body-limit.mjs";
5
6
  const FastResponse = Response;
6
7
  function serve(options) {
@@ -22,6 +23,7 @@ var DenoServer = class {
22
23
  middleware: [...options.middleware || []]
23
24
  };
24
25
  for (const plugin of options.plugins || []) plugin(this);
26
+ trustProxyPlugin(this);
25
27
  gracefulShutdownPlugin(this);
26
28
  const fetchHandler = wrapFetch(this);
27
29
  const loader = globalThis.__srvxLoader__;
@@ -49,6 +51,7 @@ var DenoServer = class {
49
51
  },
50
52
  ip: {
51
53
  enumerable: true,
54
+ configurable: true,
52
55
  get() {
53
56
  return (info?.remoteAddr)?.hostname;
54
57
  }
@@ -1,4 +1,4 @@
1
- import { Server, ServerOptions } from "../types.mjs";
1
+ import { Server, ServerOptions } from "../_chunks/types.mjs";
2
2
  declare const FastURL: typeof globalThis.URL;
3
3
  declare const FastResponse: typeof globalThis.Response;
4
4
  declare function serve(options: ServerOptions): Server;
@@ -1,4 +1,4 @@
1
- import { FetchHandler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerOptions, ServerRequest } from "../types.mjs";
1
+ import { FetchHandler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerOptions, ServerRequest, TrustProxyOption } from "../_chunks/types.mjs";
2
2
  import { FastURL } from "../_chunks/_url.mjs";
3
3
  import { Readable } from "node:stream";
4
4
  type NodeRequestContext = {
@@ -9,6 +9,11 @@ type NodeRequestContext = {
9
9
  * buffered reads and the streamed body. See `ServerOptions.maxRequestBodySize`.
10
10
  */
11
11
  maxRequestBodySize?: number;
12
+ /**
13
+ * Whether to trust `X-Forwarded-*` / `:scheme` headers when deriving the
14
+ * request protocol, host and client IP. See `ServerOptions.trustProxy`.
15
+ */
16
+ trustProxy?: TrustProxyOption;
12
17
  };
13
18
  declare const NodeRequest: {
14
19
  new (nodeCtx: NodeRequestContext): ServerRequest;
@@ -68,6 +73,7 @@ type AdapterMeta = {
68
73
  */
69
74
  declare function toNodeHandler(handler: FetchHandler & AdapterMeta, options?: {
70
75
  maxRequestBodySize?: number;
76
+ trustProxy?: TrustProxyOption;
71
77
  }): NodeHttpHandler & AdapterMeta;
72
78
  /**
73
79
  * Converts a Node.js HTTP handler into a Fetch API handler.
@@ -1,6 +1,7 @@
1
1
  import { FastURL, lazyInherit } from "../_chunks/_url.mjs";
2
2
  import { createWaitUntil, fmtURL, printListening, resolvePortAndHost, resolveTLSOptions } from "../_chunks/_utils2.mjs";
3
3
  import { errorPlugin, gracefulShutdownPlugin, wrapFetch } from "../_chunks/_plugins.mjs";
4
+ import { HOST_RE, firstForwardedValue, isTrustedProxy } from "../_chunks/_trust-proxy.mjs";
4
5
  import { createBodyTooLargeError, limitBodyStream } from "../_chunks/_body-limit.mjs";
5
6
  import nodeHTTP, { IncomingMessage, ServerResponse } from "node:http";
6
7
  import { Duplex, PassThrough, Readable, addAbortSignal } from "node:stream";
@@ -122,15 +123,16 @@ function streamBody(stream, nodeRes) {
122
123
  nodeRes.off("error", streamCancel);
123
124
  });
124
125
  }
125
- const HOST_RE = /^(\[(?:[A-Fa-f0-9:.]+)\]|(?:[A-Za-z0-9_-]+\.)*[A-Za-z0-9_-]+|(?:\d{1,3}\.){3}\d{1,3})(:\d{1,5})?$/;
126
126
  var NodeRequestURL = class extends FastURL {
127
- constructor({ req }) {
127
+ constructor({ req, trusted = false }) {
128
128
  const path = req.url || "/";
129
- let host = req.headers.host || req.headers[":authority"];
129
+ const forwardedHost = trusted ? firstForwardedValue(req.headers["x-forwarded-host"]) : void 0;
130
+ let host = (forwardedHost && HOST_RE.test(forwardedHost) ? forwardedHost : void 0) || req.headers.host || req.headers[":authority"];
130
131
  if (host && !HOST_RE.test(host)) host = "_invalid_";
131
132
  else if (!host) if (req.socket) host = `${req.socket.localFamily === "IPv6" ? "[" + req.socket.localAddress + "]" : req.socket.localAddress}:${req.socket?.localPort || "80"}`;
132
133
  else host = "localhost";
133
- const protocol = req.socket?.encrypted || req.headers["x-forwarded-proto"] === "https" || req.headers[":scheme"] === "https" ? "https:" : "http:";
134
+ const forwardedProto = trusted ? firstForwardedValue(req.headers["x-forwarded-proto"]) : void 0;
135
+ const protocol = req.socket?.encrypted || forwardedProto === "https" || trusted && req.headers[":scheme"] === "https" ? "https:" : "http:";
134
136
  if (path[0] === "/") {
135
137
  const qIndex = path.indexOf("?");
136
138
  super({
@@ -252,9 +254,15 @@ const NodeRequest = /* @__PURE__ */ (() => {
252
254
  #headers;
253
255
  #abortController;
254
256
  #maxRequestBodySize;
257
+ #trustProxy;
258
+ #ip;
259
+ #ipResolved = false;
260
+ #remoteAddress;
261
+ #trusted;
255
262
  constructor(ctx) {
256
263
  this.#req = ctx.req;
257
264
  this.#maxRequestBodySize = ctx.maxRequestBodySize;
265
+ this.#trustProxy = ctx.trustProxy;
258
266
  this.runtime = {
259
267
  name: "node",
260
268
  node: ctx
@@ -263,15 +271,31 @@ const NodeRequest = /* @__PURE__ */ (() => {
263
271
  static [Symbol.hasInstance](val) {
264
272
  return val instanceof NativeRequest;
265
273
  }
274
+ #resolveTrusted() {
275
+ if (this.#trusted === void 0) {
276
+ this.#remoteAddress = this.#req.socket?.remoteAddress;
277
+ this.#trusted = isTrustedProxy(this.#trustProxy, this.#remoteAddress);
278
+ }
279
+ return this.#trusted;
280
+ }
266
281
  get ip() {
267
- return this.#req.socket?.remoteAddress;
282
+ if (this.#ipResolved) return this.#ip;
283
+ this.#ipResolved = true;
284
+ if (this.#resolveTrusted()) {
285
+ const forwarded = firstForwardedValue(this.#req.headers["x-forwarded-for"]);
286
+ if (forwarded) return this.#ip = forwarded;
287
+ }
288
+ return this.#ip = this.#remoteAddress;
268
289
  }
269
290
  get method() {
270
291
  if (this.#request) return this.#request.method;
271
292
  return this.#req.method || "GET";
272
293
  }
273
294
  get _url() {
274
- return this.#url ||= new NodeRequestURL({ req: this.#req });
295
+ return this.#url ||= new NodeRequestURL({
296
+ req: this.#req,
297
+ trusted: this.#resolveTrusted()
298
+ });
275
299
  }
276
300
  set _url(url) {
277
301
  this.#url = url;
@@ -813,7 +837,8 @@ function toNodeHandler(handler, options) {
813
837
  const res = handler(new NodeRequest({
814
838
  req: nodeReq,
815
839
  res: nodeRes,
816
- maxRequestBodySize: options?.maxRequestBodySize
840
+ maxRequestBodySize: options?.maxRequestBodySize,
841
+ trustProxy: options?.trustProxy
817
842
  }));
818
843
  return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
819
844
  }
@@ -867,7 +892,8 @@ var NodeServer = class {
867
892
  const request = new NodeRequest({
868
893
  req: nodeReq,
869
894
  res: nodeRes,
870
- maxRequestBodySize: this.options.maxRequestBodySize
895
+ maxRequestBodySize: this.options.maxRequestBodySize,
896
+ trustProxy: this.options.trustProxy
871
897
  });
872
898
  request.waitUntil = this.#wait?.waitUntil;
873
899
  const res = fetchHandler(request);
@@ -891,11 +917,7 @@ var NodeServer = class {
891
917
  port,
892
918
  host,
893
919
  exclusive: !this.options.reusePort,
894
- ...tls ? {
895
- cert: tls.cert,
896
- key: tls.key,
897
- passphrase: tls.passphrase
898
- } : {},
920
+ ...tls,
899
921
  ...this.options.node
900
922
  };
901
923
  let server;
@@ -1,4 +1,4 @@
1
- import { Server, ServerOptions, ServerRequest } from "../types.mjs";
1
+ import { Server, ServerOptions, ServerRequest } from "../_chunks/types.mjs";
2
2
  declare const FastURL: typeof globalThis.URL;
3
3
  declare const FastResponse: typeof globalThis.Response;
4
4
  type ServiceWorkerHandler = (request: ServerRequest, event: FetchEvent) => Response | Promise<Response>;
package/dist/cli.mjs CHANGED
@@ -179,7 +179,7 @@ function getResponseFormat(res) {
179
179
  }
180
180
  const srvxMeta = {
181
181
  name: "srvx-nightly",
182
- version: "0.11.21",
182
+ version: "0.11.22-20260711-160642-e093402",
183
183
  description: "Universal Server."
184
184
  };
185
185
  function usage(mainOpts) {
package/dist/log.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ServerMiddleware } from "./types.mjs";
1
+ import { ServerMiddleware } from "./_chunks/types.mjs";
2
2
  interface LogOptions {}
3
3
  declare const log: (options?: LogOptions) => ServerMiddleware;
4
4
  export { LogOptions, log };
@@ -0,0 +1,114 @@
1
+ import { ServerPlugin } from "./_chunks/types.mjs";
2
+ import * as TLS from "node:tls";
3
+ /**
4
+ * TLS connection state for the current request.
5
+ *
6
+ * Available when the request was served over TLS. The peer certificate fields are populated only when the server requested a client certificate (`tls.requestCert`) and the client presented one.
7
+ *
8
+ * @note Available only on srvx's Node.js adapter — on Node.js, or on Deno (2.8+) via `node:https`. It is **not** available on Bun: neither native `Bun.serve` nor Bun's `node:http(s)` server exposes the peer certificate to the handler (they enforce `requestCert` / `rejectUnauthorized` at the handshake, but drop the certificate), so unlike Deno the `srvx/node` adapter is not a workaround. Verified against the real Bun runtime (Bun 1.3.14); `bun vitest` without the `--bun` flag silently falls back to Node, which can mask this. See https://github.com/oven-sh/bun/issues/16254
9
+ */
10
+ interface ServerRequestTLS {
11
+ /**
12
+ * The client (peer) certificate, if one was requested and presented.
13
+ *
14
+ * Empty object (`{}`) if the peer did not provide a certificate.
15
+ */
16
+ peerCertificate?: TLS.PeerCertificate;
17
+ /**
18
+ * `true` if the peer certificate was signed by one of the trusted CAs.
19
+ */
20
+ authorized?: boolean;
21
+ /**
22
+ * The reason the peer certificate failed verification, if any.
23
+ */
24
+ authorizationError?: Error | string;
25
+ /**
26
+ * The negotiated TLS protocol version, e.g. `"TLSv1.3"`.
27
+ */
28
+ protocol?: string | null;
29
+ /**
30
+ * The negotiated cipher suite.
31
+ */
32
+ cipher?: TLS.CipherNameAndProtocol;
33
+ }
34
+ declare module "srvx" {
35
+ interface ServerRequest {
36
+ /**
37
+ * TLS connection state, including the client (peer) certificate for mutual TLS.
38
+ *
39
+ * Populated by the {@link mtls} plugin. `undefined` when the request was not served over TLS.
40
+ */
41
+ tls?: ServerRequestTLS | undefined;
42
+ }
43
+ }
44
+ declare module "./types.ts" {
45
+ interface ServerRequest {
46
+ tls?: ServerRequestTLS | undefined;
47
+ }
48
+ }
49
+ /**
50
+ * Options for the {@link mtls} plugin.
51
+ */
52
+ interface MTLSOptions {
53
+ /**
54
+ * File path(s) or inlined CA certificate(s) in PEM format used to verify client certificates.
55
+ *
56
+ * When set, the well-known Mozilla CAs are replaced by the provided ones.
57
+ */
58
+ ca?: string | string[];
59
+ /**
60
+ * Request a certificate from connecting clients.
61
+ *
62
+ * @default true
63
+ */
64
+ requestCert?: boolean;
65
+ /**
66
+ * Reject the TLS handshake itself when the client certificate is not signed by one of
67
+ * the trusted `ca` certificates — the connection never reaches the `fetch` handler, so
68
+ * checking `request.tls.authorized` in application code is unreachable at this setting.
69
+ *
70
+ * Set to `false` to let the handshake complete regardless and instead expose the
71
+ * unverified certificate via `request.tls` with `authorized: false`, so your handler
72
+ * can decide how to respond.
73
+ *
74
+ * @default true (Node.js default when `requestCert` is enabled)
75
+ */
76
+ rejectUnauthorized?: boolean;
77
+ }
78
+ /**
79
+ * Mutual TLS (mTLS) plugin for the Node.js adapter.
80
+ *
81
+ * Requests a client certificate during the TLS handshake and exposes it — together
82
+ * with the negotiated protocol and cipher — on {@link ServerRequestTLS | `request.tls`}.
83
+ *
84
+ * Only srvx's Node.js adapter can deliver this (`import { serve } from "srvx/node"`),
85
+ * on Node.js or Deno (via `node:https`). Native `Deno.serve` / `Bun.serve` and Bun's
86
+ * `node:http(s)` server do not expose the peer certificate, so the plugin throws there
87
+ * rather than silently doing nothing. It also requires the server to be configured for
88
+ * TLS (`tls.cert` / `tls.key`) and throws otherwise, since mutual TLS cannot run over
89
+ * plain HTTP.
90
+ *
91
+ * With the default `rejectUnauthorized: true`, unauthenticated clients are rejected
92
+ * during the TLS handshake and never reach the `fetch` handler. Set it to `false` to
93
+ * let every handshake complete and enforce authorization yourself via
94
+ * `request.tls.authorized`, as in the example below.
95
+ *
96
+ * @example
97
+ * ```js
98
+ * import { serve } from "srvx/node";
99
+ * import { mtls } from "srvx/mtls";
100
+ *
101
+ * serve({
102
+ * tls: { cert, key },
103
+ * plugins: [mtls({ ca, requestCert: true, rejectUnauthorized: false })],
104
+ * fetch: (request) => {
105
+ * if (!request.tls?.authorized) {
106
+ * return new Response("client certificate required", { status: 401 });
107
+ * }
108
+ * return new Response(`Hello, ${request.tls.peerCertificate?.subject?.CN}`);
109
+ * },
110
+ * });
111
+ * ```
112
+ */
113
+ declare function mtls(options?: MTLSOptions): ServerPlugin;
114
+ export { MTLSOptions, ServerRequestTLS, mtls };
package/dist/mtls.mjs ADDED
@@ -0,0 +1,32 @@
1
+ import { resolveCertOrKey } from "./_chunks/_utils2.mjs";
2
+ function mtls(options = {}) {
3
+ return (server) => {
4
+ if (server.runtime !== "node") throw new Error(`[srvx] mtls() requires srvx's Node.js adapter (import { serve } from "srvx/node"). The "${server.runtime}" server cannot request or expose client certificates.`);
5
+ if ("Bun" in globalThis) throw new Error("[srvx] mtls() is not available on Bun: Bun does not expose the peer certificate to node:http(s) request handlers. See https://github.com/oven-sh/bun/issues/16254");
6
+ if (server.options.protocol === "http" || !server.options.tls?.cert || !server.options.tls?.key) throw new Error("[srvx] mtls() requires an HTTPS server: set `tls.cert` and `tls.key`. Mutual TLS cannot run over plain HTTP.");
7
+ let ca;
8
+ if (options.ca !== void 0) ca = (Array.isArray(options.ca) ? options.ca : [options.ca]).map((entry) => {
9
+ const resolved = resolveCertOrKey(entry);
10
+ if (!resolved) throw new TypeError("mtls() `ca` entries must be non-empty PEM strings or file paths.");
11
+ return resolved;
12
+ });
13
+ server.options.node = {
14
+ ...server.options.node,
15
+ ...ca ? { ca } : {},
16
+ requestCert: options.requestCert ?? true,
17
+ ...options.rejectUnauthorized === void 0 ? {} : { rejectUnauthorized: options.rejectUnauthorized }
18
+ };
19
+ server.options.middleware.unshift((request, next) => {
20
+ const socket = request.runtime?.node?.req?.socket;
21
+ if (socket && typeof socket.getPeerCertificate === "function") request.tls = {
22
+ peerCertificate: socket.getPeerCertificate(),
23
+ authorized: socket.authorized,
24
+ authorizationError: socket.authorizationError ?? void 0,
25
+ protocol: socket.getProtocol?.() ?? void 0,
26
+ cipher: socket.getCipher?.()
27
+ };
28
+ return next();
29
+ });
30
+ };
31
+ }
32
+ export { mtls };
package/dist/static.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ServerMiddleware } from "./types.mjs";
1
+ import { ServerMiddleware } from "./_chunks/types.mjs";
2
2
  interface ServeStaticOptions {
3
3
  /**
4
4
  * The directory to serve static files from.
@@ -1,4 +1,4 @@
1
- import { Server, ServerMiddleware, ServerPlugin, ServerRequest } from "./types.mjs";
1
+ import { Server, ServerMiddleware, ServerPlugin, ServerRequest } from "./_chunks/types.mjs";
2
2
  /**
3
3
  * @experimental Channel names, event types and config options may change in future releases.
4
4
  */
package/dist/types.d.mts CHANGED
@@ -1,325 +1,2 @@
1
- import * as NodeHttp from "node:http";
2
- import * as NodeHttps from "node:https";
3
- import * as NodeHttp2 from "node:http2";
4
- import * as AWS from "aws-lambda";
5
- import * as NodeNet from "node:net";
6
- import * as Bun from "bun";
7
- import * as CF from "@cloudflare/workers-types";
8
- type MaybePromise<T> = T | Promise<T>;
9
- type IsAny<T> = Equal<T, any> extends true ? true : false;
10
- type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
11
- /**
12
- * Faster URL constructor with lazy access to pathname and search params (For Node, Deno, and Bun).
13
- */
14
- declare const FastURL: typeof globalThis.URL;
15
- /**
16
- * Faster Response constructor optimized for Node.js (same as Response for other runtimes).
17
- */
18
- declare const FastResponse: typeof globalThis.Response;
19
- /**
20
- * Create a new server instance.
21
- */
22
- declare function serve(options: ServerOptions): Server;
23
- /**
24
- * Web fetch compatible request handler
25
- */
26
- type ServerHandler = (request: ServerRequest) => MaybePromise<Response>;
27
- type ServerMiddleware = (request: ServerRequest, next: () => Response | Promise<Response>) => Response | Promise<Response>;
28
- type ServerPlugin = (server: Server) => void;
29
- /**
30
- * Server options
31
- */
32
- interface ServerOptions {
33
- /**
34
- * The fetch handler handles incoming requests.
35
- */
36
- fetch: ServerHandler;
37
- /**
38
- * Handle lifecycle errors.
39
- *
40
- * @note This handler will set built-in Bun and Deno error handler.
41
- */
42
- error?: ErrorHandler;
43
- /**
44
- * Server middleware handlers to run before the main fetch handler.
45
- */
46
- middleware?: ServerMiddleware[];
47
- /**
48
- * Server plugins.
49
- */
50
- plugins?: ServerPlugin[];
51
- /**
52
- * If set to `true`, server will not start listening automatically.
53
- */
54
- manual?: boolean;
55
- /**
56
- * The port server should be listening to.
57
- *
58
- * Default is read from `PORT` environment variable or will be `3000`.
59
- *
60
- * **Tip:** You can set the port to `0` to use a random port.
61
- */
62
- port?: string | number;
63
- /**
64
- * The hostname (IP or resolvable host) server listener should bound to.
65
- *
66
- * When not provided, server with listen to all network interfaces by default.
67
- *
68
- * **Important:** If you are running a server that is not expected to be exposed to the network, use `hostname: "localhost"`.
69
- */
70
- hostname?: string;
71
- /**
72
- * Enabling this option allows multiple processes to bind to the same port, which is useful for load balancing.
73
- *
74
- * **Note:** Despite Node.js built-in behavior that has `exclusive` flag (opposite of `reusePort`) enabled by default, srvx uses non-exclusive mode for consistency.
75
- */
76
- reusePort?: boolean;
77
- /**
78
- * The protocol to use for the server.
79
- *
80
- * Possible values are `http` and `https`.
81
- *
82
- * 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.
83
- */
84
- protocol?: "http" | "https";
85
- /**
86
- * If set to `true`, server will not print the listening address.
87
- */
88
- silent?: boolean;
89
- /**
90
- * Graceful shutdown on SIGINT and SIGTERM signals.
91
- *
92
- * Supported for Node.js, Deno and Bun runtimes.
93
- *
94
- * @default true (disabled in test and ci environments)
95
- */
96
- gracefulShutdown?: boolean | {
97
- gracefulTimeout?: number;
98
- forceTimeout?: number;
99
- };
100
- /**
101
- * Maximum allowed size (in bytes) for the request body.
102
- *
103
- * As the body is read, its accumulated length is tracked and, once it exceeds
104
- * this limit, reading is aborted and rejects with a `413`-style error (the error
105
- * has `statusCode: 413`, `status: 413` and `code: "ERR_BODY_TOO_LARGE"`) so a
106
- * handler can map it to an HTTP 413 (Payload Too Large) response.
107
- *
108
- * The limit covers the buffered reads (`request.text()` / `request.json()`) as
109
- * well as the streamed body (`request.body`, and therefore `arrayBuffer()` /
110
- * `blob()` / `bytes()` / `formData()`).
111
- *
112
- * Runtime support:
113
- * - **Node**: enforced by srvx (body stream is size-limited).
114
- * - **Bun**: mapped to Bun's native `maxRequestBodySize` (413 before the handler).
115
- * - **Deno**: enforced by srvx (request body stream is size-limited).
116
- *
117
- * @default undefined (no limit)
118
- */
119
- maxRequestBodySize?: number;
120
- /**
121
- * TLS server options.
122
- */
123
- tls?: {
124
- /**
125
- * File path or inlined TLS certificate in PEM format (required).
126
- */
127
- cert?: string;
128
- /**
129
- * File path or inlined TLS private key in PEM format (required).
130
- */
131
- key?: string;
132
- /**
133
- * Passphrase for the private key (optional).
134
- */
135
- passphrase?: string;
136
- };
137
- /**
138
- * Node.js server options.
139
- */
140
- node?: (NodeHttp.ServerOptions | NodeHttps.ServerOptions | NodeHttp2.ServerOptions) & NodeNet.ListenOptions & {
141
- http2?: boolean;
142
- };
143
- /**
144
- * Bun server options
145
- *
146
- * @docs https://bun.sh/docs/api/http
147
- */
148
- bun?: Omit<Bun.Serve.Options<any>, "fetch">;
149
- /**
150
- * Deno server options
151
- *
152
- * @docs https://docs.deno.com/api/deno/~/Deno.serve
153
- */
154
- deno?: Deno.ServeOptions;
155
- /**
156
- * Service worker options
157
- */
158
- serviceWorker?: {
159
- /**
160
- * The path to the service worker file to be registered.
161
- */
162
- url?: string;
163
- /**
164
- * The scope of the service worker.
165
- *
166
- */
167
- scope?: string;
168
- };
169
- }
170
- interface Server<Handler = ServerHandler> {
171
- /**
172
- * Current runtime name
173
- */
174
- readonly runtime: "node" | "deno" | "bun" | "bunny" | "cloudflare" | "service-worker" | "aws-lambda" | "generic";
175
- /**
176
- * Server options
177
- */
178
- readonly options: ServerOptions & {
179
- middleware: ServerMiddleware[];
180
- };
181
- /**
182
- * Server URL address.
183
- */
184
- readonly url?: string;
185
- /**
186
- * Node.js context.
187
- */
188
- readonly node?: {
189
- server?: NodeHttp.Server | NodeHttp2.Http2Server;
190
- handler: (req: NodeServerRequest, res: NodeServerResponse) => void | Promise<void>;
191
- };
192
- /**
193
- * Bun context.
194
- */
195
- readonly bun?: {
196
- server?: Bun.Server<any>;
197
- };
198
- /**
199
- * Deno context.
200
- */
201
- readonly deno?: {
202
- server?: Deno.HttpServer;
203
- };
204
- /**
205
- * Server fetch handler
206
- */
207
- readonly fetch: Handler;
208
- /**
209
- * Start listening for incoming requests.
210
- * When `manual` option is enabled, this method needs to be called explicitly to begin accepting connections.
211
- */
212
- serve(): void | Promise<Server<Handler>>;
213
- /**
214
- * Returns a promise that resolves when the server is ready.
215
- */
216
- ready(): Promise<Server<Handler>>;
217
- /**
218
- * Register a background task that the server should await before closing.
219
- *
220
- * Same as `request.waitUntil` but available at the server level for use outside of request handlers.
221
- */
222
- readonly waitUntil?: (promise: Promise<unknown>) => void;
223
- /**
224
- * Stop listening to prevent new connections from being accepted.
225
- *
226
- * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
227
- *
228
- * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
229
- * @default false
230
- */
231
- close(closeActiveConnections?: boolean): Promise<void>;
232
- }
233
- interface ServerRuntimeContext {
234
- name: "node" | "deno" | "bun" | "bunny" | "cloudflare" | "aws-lambda" | (string & {});
235
- /**
236
- * Underlying Node.js server request info.
237
- */
238
- node?: {
239
- req: NodeServerRequest;
240
- res?: NodeServerResponse;
241
- };
242
- /**
243
- * Underlying Deno server request info.
244
- */
245
- deno?: {
246
- info: Deno.ServeHandlerInfo<Deno.NetAddr>;
247
- };
248
- /**
249
- * Underlying Bun server request context.
250
- */
251
- bun?: {
252
- server: Bun.Server<any>;
253
- };
254
- /**
255
- * Underlying Cloudflare request context.
256
- */
257
- cloudflare?: {
258
- context: CF.ExecutionContext;
259
- env: IsAny<typeof import("cloudflare:workers")> extends true ? Record<string, unknown> : typeof import("cloudflare:workers").env;
260
- };
261
- awsLambda?: {
262
- context: AWS.Context;
263
- event: AWS.APIGatewayProxyEvent | AWS.APIGatewayProxyEventV2;
264
- };
265
- serviceWorker?: {
266
- event: FetchEvent;
267
- };
268
- netlify?: {
269
- context: any;
270
- };
271
- stormkit?: {
272
- event: any;
273
- context: any;
274
- };
275
- vercel?: {
276
- context: {
277
- waitUntil?: (promise: Promise<any>) => void;
278
- };
279
- };
280
- }
281
- interface ServerRequestContext {
282
- [key: string]: unknown;
283
- }
284
- interface ServerRequest extends Request {
285
- /**
286
- * Access to Node.js native instance of request.
287
- *
288
- * See https://srvx.h3.dev/guide/node#noderequest
289
- */
290
- _request?: Request;
291
- /**
292
- * Access to the parsed URL
293
- */
294
- _url?: URL;
295
- /**
296
- * Runtime specific request context.
297
- */
298
- runtime?: ServerRuntimeContext;
299
- /**
300
- * IP address of the client.
301
- */
302
- ip?: string | undefined;
303
- /**
304
- * Arbitrary context related to the request.
305
- */
306
- context?: ServerRequestContext;
307
- /**
308
- * Tell the runtime about an ongoing operation that shouldn't close until the promise resolves.
309
- */
310
- waitUntil?: (promise: Promise<unknown>) => void | Promise<void>;
311
- }
312
- type FetchHandler = (request: Request) => Response | Promise<Response>;
313
- type ErrorHandler = (error: unknown) => Response | Promise<Response>;
314
- type BunFetchHandler = (request: Request, server?: Bun.Server<any>) => Response | Promise<Response>;
315
- type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
316
- type NodeServerRequest = NodeHttp.IncomingMessage | NodeHttp2.Http2ServerRequest;
317
- type NodeServerResponse = NodeHttp.ServerResponse | NodeHttp2.Http2ServerResponse;
318
- type NodeHttp1Handler = (req: NodeHttp.IncomingMessage, res: NodeHttp.ServerResponse) => void | Promise<void>;
319
- type NodeHttp2Handler = (req: NodeHttp2.Http2ServerRequest, res: NodeHttp2.Http2ServerResponse) => void | Promise<void>;
320
- type NodeHttpHandler = NodeHttp1Handler | NodeHttp2Handler;
321
- type NodeHTTP1Middleware = (req: NodeHttp.IncomingMessage, res: NodeHttp.ServerResponse, next: (error?: Error) => void) => unknown | Promise<unknown>;
322
- type NodeHTTP2Middleware = (req: NodeHttp2.Http2ServerRequest, res: NodeHttp2.Http2ServerResponse, next: (error?: Error) => void) => unknown | Promise<unknown>;
323
- type NodeHTTPMiddleware = NodeHTTP1Middleware | NodeHTTP2Middleware;
324
- type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
325
- export { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FastResponse, FastURL, FetchHandler, NodeHTTP1Middleware, NodeHTTP2Middleware, NodeHTTPMiddleware, NodeHttp1Handler, NodeHttp2Handler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerRequest, ServerRequestContext, ServerRuntimeContext, serve };
1
+ import { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FastResponse, FastURL, FetchHandler, NodeHTTP1Middleware, NodeHTTP2Middleware, NodeHTTPMiddleware, NodeHttp1Handler, NodeHttp2Handler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerRequest, ServerRequestContext, ServerRuntimeContext, TrustProxyOption, serve } from "./_chunks/types.mjs";
2
+ export { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FastResponse, FastURL, FetchHandler, NodeHTTP1Middleware, NodeHTTP2Middleware, NodeHTTPMiddleware, NodeHttp1Handler, NodeHttp2Handler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerRequest, ServerRequestContext, ServerRuntimeContext, type TrustProxyOption, serve };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "srvx-nightly",
3
- "version": "0.11.21",
3
+ "version": "0.11.22-20260711-160642-e093402",
4
4
  "description": "Universal Server.",
5
5
  "homepage": "https://srvx.h3.dev",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "./service-worker": "./dist/adapters/service-worker.mjs",
26
26
  "./aws-lambda": "./dist/adapters/aws-lambda.mjs",
27
27
  "./cli": "./dist/cli.mjs",
28
+ "./mtls": "./dist/mtls.mjs",
28
29
  "./static": "./dist/static.mjs",
29
30
  "./log": "./dist/log.mjs",
30
31
  "./tracing": "./dist/tracing.mjs",