@@ -25,2 +25,8 @@ import * as NodeHttp from "node:http"; | ||
| /** | ||
| * Headers accepted by the runtime `Headers` constructor (`HeadersInit`). | ||
| * | ||
| * Derived from the ambient `Headers` so that it does not require `lib: ["dom"]`. | ||
| */ | ||
| type ResponseHeaders = NonNullable<ConstructorParameters<typeof globalThis.Headers>[0]>; | ||
| /** | ||
| * Options forwarded to `Bun.serve()`. | ||
@@ -60,3 +66,3 @@ * | ||
| upgrade(request: Request, options?: { | ||
| headers?: HeadersInit; | ||
| headers?: ResponseHeaders; | ||
| data?: any; | ||
@@ -63,0 +69,0 @@ }): boolean; |
| import { errorPlugin, wrapFetch } from "../_chunks/_plugins.mjs"; | ||
| import { forwardedHopValue, resolveClientIP, trustedHops } from "../_chunks/_trust-proxy.mjs"; | ||
| import { HOST_RE, forwardedHopValue, resolveClientIP, trustedHops } from "../_chunks/_trust-proxy.mjs"; | ||
| function awsRequest(event, context, trustProxy) { | ||
@@ -32,7 +32,10 @@ const sourceIp = awsEventIP(event); | ||
| function awsEventURL(event, hops) { | ||
| const path = event.path || event.rawPath; | ||
| const rawPath = event.path || event.rawPath || "/"; | ||
| const path = rawPath[0] === "/" ? rawPath : `/${rawPath}`; | ||
| const query = awsEventQuery(event); | ||
| const hostname = forwardedHopValue(event.headers["X-Forwarded-Host"] || event.headers["x-forwarded-host"], hops) || event.headers.host || event.headers.Host || event.requestContext?.domainName || "."; | ||
| const forwardedHost = forwardedHopValue(event.headers["X-Forwarded-Host"] || event.headers["x-forwarded-host"], hops); | ||
| const host = (forwardedHost && HOST_RE.test(forwardedHost) ? forwardedHost : void 0) || event.headers.host || event.headers.Host || event.requestContext?.domainName; | ||
| const hostname = host && HOST_RE.test(host) ? host : "_invalid_"; | ||
| const protocol = forwardedHopValue(event.headers["X-Forwarded-Proto"] || event.headers["x-forwarded-proto"], hops) === "http" ? "http" : "https"; | ||
| return new URL(`${path}${query ? `?${query}` : ""}`, `${protocol}://${hostname}`); | ||
| return new URL(`${protocol}://${hostname}${path}${query ? `?${query}` : ""}`); | ||
| } | ||
@@ -49,3 +52,7 @@ function awsEventQuery(event) { | ||
| for (const [key, value] of Object.entries(event.headers)) if (value) headers.set(key, value); | ||
| if ("cookies" in event && event.cookies) for (const cookie of event.cookies) headers.append("cookie", cookie); | ||
| const cookies = "cookies" in event && event.cookies?.length ? event.cookies : void 0; | ||
| if (cookies) { | ||
| headers.delete("cookie"); | ||
| for (const cookie of cookies) headers.append("cookie", cookie); | ||
| } | ||
| return headers; | ||
@@ -142,3 +149,3 @@ } | ||
| for (const [key, value] of request.headers) { | ||
| if (key.toLowerCase() === "cookie") cookies.push(value); | ||
| if (key.toLowerCase() === "cookie") cookies.push(...splitCookieHeader(value)); | ||
| headers[key] = value; | ||
@@ -218,2 +225,5 @@ } | ||
| } | ||
| function splitCookieHeader(value) { | ||
| return value.split(";").map((c) => c.trim()).filter(Boolean); | ||
| } | ||
| function parseMultiValueQuery(params) { | ||
@@ -238,4 +248,6 @@ const result = {}; | ||
| let body; | ||
| if (typeof result.body === "string") if (result.isBase64Encoded) body = Buffer.from(result.body, "base64"); | ||
| else body = result.body; | ||
| if (typeof result.body === "string") { | ||
| if (result.isBase64Encoded) body = Buffer.from(result.body, "base64"); | ||
| else body = result.body; | ||
| } | ||
| const statusCode = typeof result.statusCode === "number" ? result.statusCode : 200; | ||
@@ -242,0 +254,0 @@ return new Response(body, { |
+142
-49
@@ -38,2 +38,3 @@ import { FastURL, lazyInherit } from "../_chunks/_url.mjs"; | ||
| nodeRes.statusCode = 500; | ||
| if (nodeRes.req?.httpVersion !== "2.0") nodeRes.statusMessage = ""; | ||
| nodeRes.end(); | ||
@@ -65,5 +66,11 @@ } | ||
| function writeHead(nodeRes, status, statusText, rawHeaders) { | ||
| if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, rawHeaders); | ||
| else nodeRes.writeHead(status, statusText, rawHeaders); | ||
| if (!nodeRes.headersSent) { | ||
| if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, rawHeaders); | ||
| else nodeRes.writeHead(status, safeStatusText(statusText), rawHeaders); | ||
| } | ||
| } | ||
| const INVALID_REASON_PHRASE_RE = /[^\t\u0020-\u007E\u0080-\u00FF]/g; | ||
| function safeStatusText(statusText) { | ||
| return typeof statusText === "string" && statusText ? statusText.replace(INVALID_REASON_PHRASE_RE, "") : statusText; | ||
| } | ||
| function endNodeResponse(nodeRes, detached) { | ||
@@ -81,2 +88,8 @@ if (detached) { | ||
| } | ||
| if (nodeRes.req?.method === "HEAD") { | ||
| if (typeof stream.destroy === "function") stream.destroy(); | ||
| else stream.abort?.(); | ||
| writeHead(nodeRes, status, statusText, headers); | ||
| return endNodeResponse(nodeRes); | ||
| } | ||
| if (typeof stream.on !== "function" || typeof stream.destroy !== "function") { | ||
@@ -92,4 +105,9 @@ writeHead(nodeRes, status, statusText, headers); | ||
| return new Promise((resolve) => { | ||
| function cleanup() { | ||
| stream.off("error", onEarlyError); | ||
| stream.off("readable", onReadable); | ||
| nodeRes.off("close", onResClose); | ||
| } | ||
| function onEarlyError() { | ||
| stream.off("readable", onReadable); | ||
| cleanup(); | ||
| stream.destroy(); | ||
@@ -100,3 +118,3 @@ writeHead(nodeRes, 500, "Internal Server Error", []); | ||
| function onReadable() { | ||
| stream.off("error", onEarlyError); | ||
| cleanup(); | ||
| if (nodeRes.destroyed) { | ||
@@ -109,4 +127,10 @@ stream.destroy(); | ||
| } | ||
| function onResClose() { | ||
| cleanup(); | ||
| stream.destroy(); | ||
| resolve(); | ||
| } | ||
| stream.once("error", onEarlyError); | ||
| stream.once("readable", onReadable); | ||
| nodeRes.once("close", onResClose); | ||
| }); | ||
@@ -152,4 +176,6 @@ } | ||
| if (host && !HOST_RE.test(host)) host = "_invalid_"; | ||
| else if (!host) if (req.socket) host = `${req.socket.localFamily === "IPv6" ? "[" + req.socket.localAddress + "]" : req.socket.localAddress}:${req.socket?.localPort || "80"}`; | ||
| else host = "localhost"; | ||
| else if (!host) { | ||
| if (req.socket) host = `${req.socket.localFamily === "IPv6" ? "[" + req.socket.localAddress + "]" : req.socket.localAddress}:${req.socket?.localPort || "80"}`; | ||
| else host = "localhost"; | ||
| } | ||
| const forwardedProto = forwardedHopValue(req.headers["x-forwarded-proto"], hops); | ||
@@ -171,5 +197,27 @@ const protocol = req.socket?.encrypted || forwardedProto === "https" || trusted && req.headers[":scheme"] === "https" ? "https:" : "http:"; | ||
| }); | ||
| else super(path); | ||
| else { | ||
| const target = URL.canParse(path) ? new URL(path) : void 0; | ||
| if (target) { | ||
| const targetHost = target.host; | ||
| const targetPath = target.pathname; | ||
| super({ | ||
| protocol, | ||
| host: targetHost ? HOST_RE.test(targetHost) ? targetHost : "_invalid_" : host, | ||
| pathname: targetPath ? targetPath[0] === "/" ? targetPath : `/${targetPath}` : "/", | ||
| search: target.search | ||
| }); | ||
| } else super({ | ||
| protocol, | ||
| host, | ||
| pathname: "/", | ||
| search: "" | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| function isValidAbsoluteForm(target) { | ||
| if (!URL.canParse(target)) return false; | ||
| const url = new URL(target); | ||
| return (url.protocol === "http:" || url.protocol === "https:") && url.host !== ""; | ||
| } | ||
| const _nonJoinedHeaders = /* @__PURE__ */ new Set([ | ||
@@ -273,2 +321,13 @@ "age", | ||
| } | ||
| function abortError() { | ||
| return new DOMException("The request was aborted.", "AbortError"); | ||
| } | ||
| function erroredStream(error) { | ||
| return new ReadableStream({ start(controller) { | ||
| controller.error(error); | ||
| } }); | ||
| } | ||
| function isClientGone(req) { | ||
| return req.aborted || !!req.errored || req.destroyed && !req.complete; | ||
| } | ||
| const NodeRequest = /* @__PURE__ */ (() => { | ||
@@ -347,10 +406,17 @@ const NativeRequest = getNativeRequest(); | ||
| const abort = (err) => abortController.abort?.(err); | ||
| if (res) res.once("close", () => { | ||
| const reqError = req.errored; | ||
| if (reqError) abort(reqError); | ||
| else if (!res.writableEnded) abort(); | ||
| }); | ||
| else req.once("close", () => { | ||
| if (!req.complete) abort(); | ||
| }); | ||
| if (res) { | ||
| const onClose = () => { | ||
| const reqError = req.errored; | ||
| if (reqError) abort(reqError); | ||
| else if (!res.writableEnded) abort(); | ||
| }; | ||
| res.once("close", onClose); | ||
| if (res.destroyed || isClientGone(req)) onClose(); | ||
| } else { | ||
| const onClose = () => { | ||
| if (!req.complete || req.aborted) abort(); | ||
| }; | ||
| req.once("close", onClose); | ||
| if (isClientGone(req)) onClose(); | ||
| } | ||
| } | ||
@@ -369,3 +435,7 @@ return this.#abortController; | ||
| if (this.#bodyStream === void 0) { | ||
| let stream = this.#hasBody() && !this.#bodyUsed ? Readable.toWeb(this.#req) : null; | ||
| let stream = null; | ||
| if (this.#hasBody() && !this.#bodyUsed) { | ||
| stream = Readable.toWeb(this.#req); | ||
| if (Readable.isDisturbed(stream)) stream = erroredStream(this.#bodyError()); | ||
| } | ||
| if (stream && this.#maxRequestBodySize !== void 0) stream = limitBodyStream(stream, this.#maxRequestBodySize); | ||
@@ -384,3 +454,9 @@ this.#bodyStream = stream; | ||
| } | ||
| #bodyError() { | ||
| const signal = this._abortController.signal; | ||
| if (signal.aborted) return signal.reason; | ||
| return this.#req.errored || (isClientGone(this.#req) ? abortError() : bodyUnusable()); | ||
| } | ||
| #readBuffered() { | ||
| if (isClientGone(this.#req) || this.#req.destroyed || this.#req.readableEnded) return Promise.reject(this.#bodyError()); | ||
| return readBody(this.#req, this.#maxRequestBodySize); | ||
@@ -481,2 +557,3 @@ } | ||
| req.off("error", onError); | ||
| req.off("close", onClose); | ||
| }; | ||
@@ -501,5 +578,13 @@ const onData = (chunk) => { | ||
| cleanup(); | ||
| if (isClientGone(req)) { | ||
| reject(req.errored || abortError()); | ||
| return; | ||
| } | ||
| resolve(chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)); | ||
| }; | ||
| req.on("data", onData).once("end", onEnd).once("error", onError); | ||
| const onClose = () => { | ||
| cleanup(); | ||
| reject(req.errored || abortError()); | ||
| }; | ||
| req.on("data", onData).once("end", onEnd).once("error", onError).once("close", onClose); | ||
| }); | ||
@@ -583,22 +668,24 @@ } | ||
| if (this.#response) body = this.#response.body; | ||
| else if (this.#body != null) if (this.#body instanceof ReadableStream) body = this.#body; | ||
| else if (typeof this.#body === "string") { | ||
| body = this.#body; | ||
| contentType = "text/plain; charset=UTF-8"; | ||
| contentLength = Buffer.byteLength(this.#body); | ||
| } else if (this.#body instanceof ArrayBuffer) { | ||
| body = Buffer.from(this.#body); | ||
| contentLength = this.#body.byteLength; | ||
| } else if (this.#body instanceof Uint8Array) { | ||
| body = this.#body; | ||
| contentLength = this.#body.byteLength; | ||
| } else if (this.#body instanceof DataView) { | ||
| body = Buffer.from(this.#body.buffer, this.#body.byteOffset, this.#body.byteLength); | ||
| contentLength = this.#body.byteLength; | ||
| } else if (this.#body instanceof Blob) { | ||
| body = this.#body.stream(); | ||
| contentType = this.#body.type; | ||
| contentLength = this.#body.size; | ||
| } else if (typeof this.#body.pipe === "function") body = this.#body; | ||
| else body = this._response.body; | ||
| else if (this.#body != null) { | ||
| if (this.#body instanceof ReadableStream) body = this.#body; | ||
| else if (typeof this.#body === "string") { | ||
| body = this.#body; | ||
| contentType = "text/plain; charset=UTF-8"; | ||
| contentLength = Buffer.byteLength(this.#body); | ||
| } else if (this.#body instanceof ArrayBuffer) { | ||
| body = Buffer.from(this.#body); | ||
| contentLength = this.#body.byteLength; | ||
| } else if (this.#body instanceof Uint8Array) { | ||
| body = this.#body; | ||
| contentLength = this.#body.byteLength; | ||
| } else if (this.#body instanceof DataView) { | ||
| body = Buffer.from(this.#body.buffer, this.#body.byteOffset, this.#body.byteLength); | ||
| contentLength = this.#body.byteLength; | ||
| } else if (this.#body instanceof Blob) { | ||
| body = this.#body.stream(); | ||
| contentType = this.#body.type; | ||
| contentLength = this.#body.size; | ||
| } else if (typeof this.#body.pipe === "function") body = this.#body; | ||
| else body = this._response.body; | ||
| } | ||
| const headers = []; | ||
@@ -787,2 +874,3 @@ const initHeaders = this.#init?.headers; | ||
| this.httpVersion = "1.1"; | ||
| const headers = {}; | ||
| const rawHeaders = this.rawHeaders; | ||
@@ -792,3 +880,3 @@ for (const [key, value] of req.headers.entries()) { | ||
| if (lowerKey === "set-cookie") continue; | ||
| this.headers[lowerKey] = value; | ||
| headers[lowerKey] = value; | ||
| rawHeaders.push(key, value); | ||
@@ -798,6 +886,7 @@ } | ||
| if (setCookie.length > 0) { | ||
| this.headers["set-cookie"] = setCookie; | ||
| headers["set-cookie"] = setCookie; | ||
| for (const cookie of setCookie) rawHeaders.push("set-cookie", cookie); | ||
| } | ||
| if (req.method !== "GET" && req.method !== "HEAD" && !this.headers["content-length"] && !this.headers["transfer-encoding"]) this.headers["transfer-encoding"] = "chunked"; | ||
| if (req.method !== "GET" && req.method !== "HEAD" && !headers["content-length"] && !headers["transfer-encoding"]) headers["transfer-encoding"] = "chunked"; | ||
| this.headers = headers; | ||
| const onData = (chunk) => { | ||
@@ -911,3 +1000,3 @@ if (!this.push(chunk)) socket.pause(); | ||
| writeHead(statusCode, statusMessage, headers) { | ||
| const result = typeof statusMessage === "string" ? super.writeHead(statusCode, statusMessage, stripTransferEncoding(headers)) : super.writeHead(statusCode, stripTransferEncoding(statusMessage)); | ||
| const result = typeof statusMessage === "string" ? super.writeHead(statusCode, statusMessage, stripTransferEncoding(headers)) : super.writeHead(statusCode, stripTransferEncoding(headers ?? statusMessage)); | ||
| this.#onHeadersSent?.(); | ||
@@ -1068,3 +1157,3 @@ return result; | ||
| })); | ||
| return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res); | ||
| return res instanceof Promise ? res.then((resolvedRes) => send(nodeRes, resolvedRes)) : send(nodeRes, res); | ||
| } | ||
@@ -1075,2 +1164,5 @@ convertedNodeHandler.__fetchHandler = handler; | ||
| } | ||
| function send(nodeRes, webRes) { | ||
| return sendNodeResponse(nodeRes, webRes).catch((error) => handleSendError(nodeRes, error)); | ||
| } | ||
| function toFetchHandler(handler) { | ||
@@ -1114,3 +1206,3 @@ if (handler.__fetchHandler) return handler.__fetchHandler; | ||
| const reqUrl = nodeReq.url; | ||
| if (reqUrl && reqUrl[0] !== "/" && reqUrl !== "*" && !URL.canParse(reqUrl)) { | ||
| if (reqUrl && reqUrl[0] !== "/" && reqUrl !== "*" && !isValidAbsoluteForm(reqUrl)) { | ||
| nodeRes.statusCode = 400; | ||
@@ -1159,8 +1251,9 @@ nodeRes.end(); | ||
| this.#isSecure = !!this.serveOptions.cert && this.options.protocol !== "http"; | ||
| if (this.options.node?.http2 ?? this.#isSecure) if (this.#isSecure) server = nodeHTTP2.createSecureServer({ | ||
| allowHTTP1: true, | ||
| ...this.serveOptions | ||
| }, handler); | ||
| else throw new Error("node.http2 option requires tls certificate!"); | ||
| else if (this.#isSecure) server = nodeHTTPS.createServer(this.serveOptions, handler); | ||
| if (this.options.node?.http2 ?? this.#isSecure) { | ||
| if (this.#isSecure) server = nodeHTTP2.createSecureServer({ | ||
| allowHTTP1: true, | ||
| ...this.serveOptions | ||
| }, handler); | ||
| else throw new Error("node.http2 option requires tls certificate!"); | ||
| } else if (this.#isSecure) server = nodeHTTPS.createServer(this.serveOptions, handler); | ||
| else server = nodeHTTP.createServer(this.serveOptions, handler); | ||
@@ -1167,0 +1260,0 @@ this.node.server = server; |
@@ -11,2 +11,7 @@ /** | ||
| * | ||
| * srvx's `ServerRequest._request` escape hatch is served over the same limited | ||
| * stream, so unwrapping the request (`req._request`, or `new Request(req)` under | ||
| * `patchGlobalRequest()`, which rewrites the input to `_request`) cannot reach | ||
| * the raw body and bypass the limit. | ||
| * | ||
| * Proxy-wrapping (rather than rebuilding via `new Request(request, …)`) is | ||
@@ -13,0 +18,0 @@ * deliberate: it preserves the exact object handed in — including srvx's |
@@ -10,2 +10,3 @@ function limitRequestBody(request, maxRequestBodySize, options) { | ||
| let limited; | ||
| let nativeRequest; | ||
| const limitedBody = () => limited ??= new Response(overLimit ? erroredStream(createError(maxRequestBodySize)) : limitBodyStream(request.body, maxRequestBodySize, options)); | ||
@@ -16,2 +17,9 @@ return new Proxy(request, { get(target, prop) { | ||
| if (typeof prop === "string" && bodyReadMethods.has(prop)) return () => limitedBody()[prop](); | ||
| if (prop === "_request" && "_request" in target) return nativeRequest ??= new Request(target.url, { | ||
| method: target.method, | ||
| headers: target.headers, | ||
| signal: target.signal, | ||
| body: limitedBody().body, | ||
| duplex: "half" | ||
| }); | ||
| if (prop === "clone") return () => limitRequestBody(target.clone(), maxRequestBodySize, options); | ||
@@ -18,0 +26,0 @@ const value = Reflect.get(target, prop, target); |
+34
-16
@@ -21,3 +21,7 @@ import { bold, cyan, gray, green, magenta, red, url, yellow } from "./_chunks/_utils.mjs"; | ||
| }); | ||
| const { serve: srvxServe } = loaded.nodeCompat ? await import("srvx/node") : await import("srvx"); | ||
| const nested = loaded.srvxServer?.options; | ||
| const nestedNode = nested?.node; | ||
| const nestedMTLS = !!nestedNode && ("requestCert" in nestedNode || "ca" in nestedNode); | ||
| if (nestedMTLS && loaded.srvxServer?.runtime !== "node") throw new Error(`[srvx] The server entry configures mutual TLS, which requires srvx's Node.js adapter (import { serve } from "srvx/node").`); | ||
| const { serve: srvxServe } = loaded.nodeCompat || nestedMTLS ? await import("srvx/node") : await import("srvx"); | ||
| const { staticMiddleware } = await import("srvx/static"); | ||
@@ -39,3 +43,4 @@ const { loggerMiddleware } = await import("srvx/log"); | ||
| }; | ||
| let tls = serverOptions.tls; | ||
| let tls = serverOptions.tls ?? nested?.tls; | ||
| let protocol = serverOptions.protocol ?? nested?.protocol; | ||
| if (cliOpts.tls) { | ||
@@ -47,3 +52,8 @@ if (!cliOpts.cert || !cliOpts.key) throw new Error("--tls requires both --cert and --key."); | ||
| }; | ||
| protocol = void 0; | ||
| } | ||
| const nodeOptions = nested?.node || serverOptions.node ? { | ||
| ...nested?.node, | ||
| ...serverOptions.node | ||
| } : void 0; | ||
| printInfo(cliOpts, loaded); | ||
@@ -56,2 +66,4 @@ server = srvxServe({ | ||
| tls, | ||
| protocol, | ||
| node: nodeOptions, | ||
| error: (error) => { | ||
@@ -77,3 +89,3 @@ console.error(error); | ||
| let html = `<!DOCTYPE html><html><head><title>${safeTitle}</title></head><body>`; | ||
| if (cliOpts.prod) html += `<h1>${safeTitle}</h1><p>Something went wrong while processing your request.</p>`; | ||
| if (cliOpts.prod || process.env.NODE_ENV === "production") html += `<h1>${safeTitle}</h1><p>Something went wrong while processing your request.</p>`; | ||
| else html += ` | ||
@@ -159,12 +171,14 @@ <style> | ||
| let isStream = false; | ||
| if (cliOpts.data !== void 0) if (cliOpts.data === "@-") { | ||
| isStream = true; | ||
| body = new ReadableStream({ async start(controller) { | ||
| for await (const chunk of stdin) controller.enqueue(chunk); | ||
| controller.close(); | ||
| } }); | ||
| } else if (cliOpts.data.startsWith("@")) { | ||
| isStream = true; | ||
| body = Readable.toWeb(createReadStream(cliOpts.data.slice(1))); | ||
| } else body = cliOpts.data; | ||
| if (cliOpts.data !== void 0) { | ||
| if (cliOpts.data === "@-") { | ||
| isStream = true; | ||
| body = new ReadableStream({ async start(controller) { | ||
| for await (const chunk of stdin) controller.enqueue(chunk); | ||
| controller.close(); | ||
| } }); | ||
| } else if (cliOpts.data.startsWith("@")) { | ||
| isStream = true; | ||
| body = Readable.toWeb(createReadStream(cliOpts.data.slice(1))); | ||
| } else body = cliOpts.data; | ||
| } | ||
| const method = cliOpts.method || (body === void 0 ? "GET" : "POST"); | ||
@@ -214,3 +228,3 @@ const url = new URL(inputURL, `http${cliOpts.tls ? "s" : ""}://${cliOpts.host || cliOpts.hostname || "localhost"}`); | ||
| name: "srvx", | ||
| version: "0.12.4", | ||
| version: "0.12.5", | ||
| description: "Universal Server." | ||
@@ -389,2 +403,3 @@ }; | ||
| } else if (sub === "serve") positionals.shift(); | ||
| const prod = values.prod ?? process.env.NODE_ENV === "production"; | ||
| if (mode === "fetch") { | ||
@@ -396,2 +411,3 @@ const method = values.method || values.request; | ||
| ...values, | ||
| prod, | ||
| url, | ||
@@ -410,3 +426,4 @@ method | ||
| mode, | ||
| ...values | ||
| ...values, | ||
| prod | ||
| }; | ||
@@ -419,3 +436,4 @@ } | ||
| async function forkCLI(args, runtimeArgs) { | ||
| const child = fork(fileURLToPath(globalThis.__SRVX_BIN__ || new URL("../bin/srvx.mjs", import.meta.url)), [...args], { execArgv: [...process.execArgv, ...runtimeArgs].filter(Boolean) }); | ||
| const srvxBin = fileURLToPath(globalThis.__SRVX_BIN__ || new URL("../bin/srvx.mjs", import.meta.url)); | ||
| const child = fork(srvxBin, [...args], { execArgv: [...process.execArgv, ...runtimeArgs].filter(Boolean) }); | ||
| child.on("error", (error) => { | ||
@@ -422,0 +440,0 @@ console.error("Error in child process:", error); |
+7
-6
@@ -20,9 +20,10 @@ import { fileURLToPath, pathToFileURL } from "node:url"; | ||
| let entry = opts.entry; | ||
| if (entry) if (entry.startsWith("file://")) { | ||
| if (!existsSync(fileURLToPath(entry))) return { notFound: true }; | ||
| if (entry) { | ||
| if (entry.startsWith("file://")) { | ||
| if (!existsSync(fileURLToPath(entry))) return { notFound: true }; | ||
| } else { | ||
| entry = resolve(opts.dir || ".", entry); | ||
| if (!existsSync(entry)) return { notFound: true }; | ||
| } | ||
| } else { | ||
| entry = resolve(opts.dir || ".", entry); | ||
| if (!existsSync(entry)) return { notFound: true }; | ||
| } | ||
| else { | ||
| for (const defEntry of defaultEntries) { | ||
@@ -29,0 +30,0 @@ for (const defExt of defaultExts) { |
+2
-1
@@ -89,3 +89,4 @@ import { ServerPlugin } from "./_chunks/types.mjs"; | ||
| * TLS (`tls.cert` / `tls.key`) and throws otherwise, since mutual TLS cannot run over | ||
| * plain HTTP. | ||
| * plain HTTP. A request that still reaches the plugin over a plain socket (TLS dropped | ||
| * after construction by an outer host) is answered with `496` rather than run. | ||
| * | ||
@@ -92,0 +93,0 @@ * With the default `rejectUnauthorized: true`, unauthenticated clients are rejected |
+5
-1
@@ -24,3 +24,7 @@ import { resolveCertOrKey } from "./_chunks/_utils2.mjs"; | ||
| const socket = request.runtime?.node?.req?.socket; | ||
| if (socket && typeof socket.getPeerCertificate === "function") request.tls = { | ||
| if (!socket || typeof socket.getPeerCertificate !== "function") return new Response("Client certificate required", { | ||
| status: 496, | ||
| headers: { "content-type": "text/plain; charset=UTF-8" } | ||
| }); | ||
| request.tls = { | ||
| peerCertificate: socket.getPeerCertificate(), | ||
@@ -27,0 +31,0 @@ authorized: socket.authorized, |
+15
-12
@@ -42,3 +42,3 @@ import { FastURL } from "./_chunks/_url.mjs"; | ||
| const COMPRESS_MIN_SIZE = 1024; | ||
| const COMPRESS_MAX_SIZE = 10 * 1024 * 1024; | ||
| const COMPRESS_MAX_SIZE = 10485760; | ||
| const COMPRESSORS = { | ||
@@ -233,13 +233,16 @@ br: (sizeHint) => createBrotliCompress({ params: { | ||
| } | ||
| if (parsed) return new FastResponse(file.handle.createReadStream({ | ||
| start: parsed.start, | ||
| end: parsed.end | ||
| }), { | ||
| status: 206, | ||
| headers: { | ||
| ...headers, | ||
| "Content-Range": `bytes ${parsed.start}-${parsed.end}/${file.size}`, | ||
| "Content-Length": (parsed.end - parsed.start + 1).toString() | ||
| } | ||
| }); | ||
| if (parsed) { | ||
| const stream = file.handle.createReadStream({ | ||
| start: parsed.start, | ||
| end: parsed.end | ||
| }); | ||
| return new FastResponse(stream, { | ||
| status: 206, | ||
| headers: { | ||
| ...headers, | ||
| "Content-Range": `bytes ${parsed.start}-${parsed.end}/${file.size}`, | ||
| "Content-Length": (parsed.end - parsed.start + 1).toString() | ||
| } | ||
| }); | ||
| } | ||
| } | ||
@@ -246,0 +249,0 @@ } |
+15
-15
| { | ||
| "name": "srvx", | ||
| "version": "0.12.5", | ||
| "version": "0.12.6", | ||
| "description": "Universal Server.", | ||
@@ -59,5 +59,5 @@ "homepage": "https://srvx.h3.dev", | ||
| "test:node-compat:deno": "deno run vitest test/node.test", | ||
| "test:node-compat:bun": "bun vitest test/node.test.ts", | ||
| "test:node-compat:bun": "bun --bun run vitest run test/node.test.ts", | ||
| "test:node-adapters:deno": "deno run vitest test/node-adapters.test", | ||
| "test:node-adapters:bun": "bun vitest test/node-adapters.test.ts", | ||
| "test:node-adapters:bun": "bun --bun run vitest run test/node-adapters.test.ts", | ||
| "typecheck": "tsc --noEmit --skipLibCheck", | ||
@@ -67,4 +67,4 @@ "vitest": "vitest" | ||
| "devDependencies": { | ||
| "@cloudflare/workers-types": "^5", | ||
| "@hono/node-server": "^2.0.10", | ||
| "@cloudflare/workers-types": "^5.20260818.1", | ||
| "@hono/node-server": "^2.1.1", | ||
| "@mitata/counters": "^0.0.8", | ||
@@ -76,6 +76,6 @@ "@mjackson/node-fetch-server": "^0.7.0", | ||
| "@types/express": "^5.0.6", | ||
| "@types/node": "^26.1.1", | ||
| "@types/node": "^26.2.0", | ||
| "@types/node-forge": "^1.3.14", | ||
| "@types/serviceworker": "^0.0.199", | ||
| "@vitest/coverage-v8": "^4.1.10", | ||
| "@types/serviceworker": "^0.0.200", | ||
| "@vitest/coverage-v8": "^4.1.11", | ||
| "@whatwg-node/server": "^0.11.0", | ||
@@ -85,5 +85,5 @@ "automd": "^0.4.3", | ||
| "eslint-config-unjs": "^0.6.2", | ||
| "execa": "^9.6.1", | ||
| "execa": "^10.0.1", | ||
| "express": "^5.2.1", | ||
| "fastify": "^5.10.0", | ||
| "fastify": "^5.12.1", | ||
| "get-port-please": "^3.2.0", | ||
@@ -94,9 +94,9 @@ "mdbox": "^0.1.1", | ||
| "obuild": "^0.4.38", | ||
| "oxfmt": ">=0.58.0", | ||
| "oxlint": "^1.73.0", | ||
| "srvx-release": "npm:srvx@^0.11.21", | ||
| "oxfmt": "^0.64.0", | ||
| "oxlint": "^1.79.0", | ||
| "srvx-release": "npm:srvx@^0.12.5", | ||
| "tslib": "^2.8.1", | ||
| "typescript": "^7.0.2", | ||
| "undici": "8.4.0", | ||
| "vitest": "^4.1.10" | ||
| "undici": "^8.10.0", | ||
| "vitest": "^4.1.11" | ||
| }, | ||
@@ -103,0 +103,0 @@ "resolutions": { |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
180176
2.98%3654
3.95%13
18.18%