srvx 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,10 @@
1
- import { t as lazyInherit } from "../_chunks/_inherit.mjs";
2
- import { t as FastURL } from "../_chunks/_url.mjs";
3
- import { a as resolveTLSOptions, i as resolvePortAndHost, n as fmtURL, r as printListening, t as createWaitUntil } from "../_chunks/_utils.mjs";
1
+ import { n as lazyInherit, t as FastURL } from "../_chunks/_url.mjs";
2
+ import { a as resolveTLSOptions, i as resolvePortAndHost, n as fmtURL, r as printListening, t as createWaitUntil } from "../_chunks/_utils2.mjs";
4
3
  import { n as gracefulShutdownPlugin, r as wrapFetch, t as errorPlugin } from "../_chunks/_plugins.mjs";
5
- import { n as NodeResponse, t as callNodeHandler } from "../_chunks/call.mjs";
6
4
  import nodeHTTP, { IncomingMessage, ServerResponse } from "node:http";
7
5
  import { Duplex, Readable } from "node:stream";
8
6
  import nodeHTTPS from "node:https";
9
7
  import nodeHTTP2 from "node:http2";
10
-
11
- //#region src/adapters/_node/send.ts
12
8
  async function sendNodeResponse(nodeRes, webRes) {
13
9
  if (!webRes) {
14
10
  nodeRes.statusCode = 500;
@@ -66,9 +62,6 @@ function streamBody(stream, nodeRes) {
66
62
  nodeRes.off("error", streamCancel);
67
63
  });
68
64
  }
69
-
70
- //#endregion
71
- //#region src/adapters/_node/url.ts
72
65
  var NodeRequestURL = class extends FastURL {
73
66
  #req;
74
67
  constructor({ req }) {
@@ -96,9 +89,6 @@ var NodeRequestURL = class extends FastURL {
96
89
  this.#req.url = this._url.pathname + this._url.search;
97
90
  }
98
91
  };
99
-
100
- //#endregion
101
- //#region src/adapters/_node/headers.ts
102
92
  const NodeRequestHeaders = /* @__PURE__ */ (() => {
103
93
  const NativeHeaders = globalThis.Headers;
104
94
  class Headers {
@@ -160,9 +150,6 @@ const NodeRequestHeaders = /* @__PURE__ */ (() => {
160
150
  Object.setPrototypeOf(Headers.prototype, NativeHeaders.prototype);
161
151
  return Headers;
162
152
  })();
163
-
164
- //#endregion
165
- //#region src/adapters/_node/request.ts
166
153
  const NodeRequest = /* @__PURE__ */ (() => {
167
154
  const NativeRequest = globalThis.Request;
168
155
  class Request {
@@ -210,7 +197,6 @@ const NodeRequest = /* @__PURE__ */ (() => {
210
197
  const { req, res } = this.runtime.node;
211
198
  const abortController = this.#abortController;
212
199
  const abort = (err) => abortController.abort?.(err);
213
- req.once("error", abort);
214
200
  if (res) res.once("close", () => {
215
201
  const reqError = req.errored;
216
202
  if (reqError) abort(reqError);
@@ -262,13 +248,6 @@ const NodeRequest = /* @__PURE__ */ (() => {
262
248
  Object.setPrototypeOf(Request.prototype, NativeRequest.prototype);
263
249
  return Request;
264
250
  })();
265
- /**
266
- * Undici uses an incompatible Request constructor depending on private property accessors.
267
- *
268
- * This utility, patches global Request to support `new Request(req)` in Node.js.
269
- *
270
- * Alternatively you can use `new Request(req._request || req)` instead of patching global Request.
271
- */
272
251
  function patchGlobalRequest() {
273
252
  const NativeRequest = globalThis[Symbol.for("srvx.nativeRequest")] ??= globalThis.Request;
274
253
  const PatchedRequest = class Request extends NativeRequest {
@@ -286,6 +265,7 @@ function patchGlobalRequest() {
286
265
  return PatchedRequest;
287
266
  }
288
267
  function readBody(req) {
268
+ if ("rawBody" in req && Buffer.isBuffer(req.rawBody)) return Promise.resolve(req.rawBody);
289
269
  return new Promise((resolve, reject) => {
290
270
  const chunks = [];
291
271
  const onData = (chunk) => {
@@ -302,35 +282,106 @@ function readBody(req) {
302
282
  req.on("data", onData).once("end", onEnd).once("error", onError);
303
283
  });
304
284
  }
305
-
306
- //#endregion
307
- //#region src/adapters/_node/web/incoming.ts
308
- var WebIncomingMessage = class extends IncomingMessage {
309
- constructor(req, socket) {
310
- super(socket);
311
- this.method = req.method;
312
- const url = req._url ??= new FastURL(req.url);
313
- this.url = url.pathname + url.search;
314
- for (const [key, value] of req.headers.entries()) this.headers[key.toLowerCase()] = value;
315
- if (req.method !== "GET" && req.method !== "HEAD" && !this.headers["content-length"] && !this.headers["transfer-encoding"]) this.headers["transfer-encoding"] = "chunked";
316
- const onData = (chunk) => {
317
- this.push(chunk);
318
- };
319
- socket.on("data", onData);
320
- socket.once("end", () => {
321
- this.emit("end");
322
- this.off("data", onData);
323
- });
285
+ const NodeResponse = /* @__PURE__ */ (() => {
286
+ const NativeResponse = globalThis.Response;
287
+ const STATUS_CODES = globalThis.process?.getBuiltinModule?.("node:http")?.STATUS_CODES || {};
288
+ class NodeResponse {
289
+ #body;
290
+ #init;
291
+ #headers;
292
+ #response;
293
+ constructor(body, init) {
294
+ this.#body = body;
295
+ this.#init = init;
296
+ }
297
+ static [Symbol.hasInstance](val) {
298
+ return val instanceof NativeResponse;
299
+ }
300
+ get status() {
301
+ return this.#response?.status || this.#init?.status || 200;
302
+ }
303
+ get statusText() {
304
+ return this.#response?.statusText || this.#init?.statusText || STATUS_CODES[this.status] || "";
305
+ }
306
+ get headers() {
307
+ if (this.#response) return this.#response.headers;
308
+ if (this.#headers) return this.#headers;
309
+ const initHeaders = this.#init?.headers;
310
+ return this.#headers = initHeaders instanceof Headers ? initHeaders : new Headers(initHeaders);
311
+ }
312
+ get ok() {
313
+ if (this.#response) return this.#response.ok;
314
+ const status = this.status;
315
+ return status >= 200 && status < 300;
316
+ }
317
+ get _response() {
318
+ if (this.#response) return this.#response;
319
+ this.#response = new NativeResponse(this.#body, this.#headers ? {
320
+ ...this.#init,
321
+ headers: this.#headers
322
+ } : this.#init);
323
+ this.#init = void 0;
324
+ this.#headers = void 0;
325
+ this.#body = void 0;
326
+ return this.#response;
327
+ }
328
+ _toNodeResponse() {
329
+ const status = this.status;
330
+ const statusText = this.statusText;
331
+ let body;
332
+ let contentType;
333
+ let contentLength;
334
+ if (this.#response) body = this.#response.body;
335
+ else if (this.#body) if (this.#body instanceof ReadableStream) body = this.#body;
336
+ else if (typeof this.#body === "string") {
337
+ body = this.#body;
338
+ contentType = "text/plain; charset=UTF-8";
339
+ contentLength = Buffer.byteLength(this.#body);
340
+ } else if (this.#body instanceof ArrayBuffer) {
341
+ body = Buffer.from(this.#body);
342
+ contentLength = this.#body.byteLength;
343
+ } else if (this.#body instanceof Uint8Array) {
344
+ body = this.#body;
345
+ contentLength = this.#body.byteLength;
346
+ } else if (this.#body instanceof DataView) {
347
+ body = Buffer.from(this.#body.buffer);
348
+ contentLength = this.#body.byteLength;
349
+ } else if (this.#body instanceof Blob) {
350
+ body = this.#body.stream();
351
+ contentType = this.#body.type;
352
+ contentLength = this.#body.size;
353
+ } else if (typeof this.#body.pipe === "function") body = this.#body;
354
+ else body = this._response.body;
355
+ const headers = [];
356
+ const initHeaders = this.#init?.headers;
357
+ const headerEntries = this.#response?.headers || this.#headers || (initHeaders ? Array.isArray(initHeaders) ? initHeaders : initHeaders?.entries ? initHeaders.entries() : Object.entries(initHeaders).map(([k, v]) => [k.toLowerCase(), v]) : void 0);
358
+ let hasContentTypeHeader;
359
+ let hasContentLength;
360
+ if (headerEntries) for (const [key, value] of headerEntries) {
361
+ if (Array.isArray(value)) for (const v of value) headers.push([key, v]);
362
+ else headers.push([key, value]);
363
+ if (key === "content-type") hasContentTypeHeader = true;
364
+ else if (key === "content-length") hasContentLength = true;
365
+ }
366
+ if (contentType && !hasContentTypeHeader) headers.push(["content-type", contentType]);
367
+ if (contentLength && !hasContentLength) headers.push(["content-length", String(contentLength)]);
368
+ this.#init = void 0;
369
+ this.#headers = void 0;
370
+ this.#response = void 0;
371
+ this.#body = void 0;
372
+ return {
373
+ status,
374
+ statusText,
375
+ headers,
376
+ body
377
+ };
378
+ }
324
379
  }
325
- };
326
-
327
- //#endregion
328
- //#region src/adapters/_node/web/socket.ts
329
- /**
330
- * Events:
331
- * - Readable (req from client): readable => data => end (push(null)) => error => close
332
- * - Writable (res to client): pipe => unpipe => drain => finish (end called) => error => close
333
- */
380
+ lazyInherit(NodeResponse.prototype, NativeResponse.prototype, "_response");
381
+ Object.setPrototypeOf(NodeResponse, NativeResponse);
382
+ Object.setPrototypeOf(NodeResponse.prototype, NativeResponse.prototype);
383
+ return NodeResponse;
384
+ })();
334
385
  var WebRequestSocket = class extends Duplex {
335
386
  _httpMessage;
336
387
  autoSelectFamilyAttemptedAddresses = [];
@@ -446,9 +497,67 @@ var WebRequestSocket = class extends Duplex {
446
497
  cb(err ?? void 0);
447
498
  }
448
499
  };
449
-
450
- //#endregion
451
- //#region src/adapters/_node/web/response.ts
500
+ var WebIncomingMessage = class extends IncomingMessage {
501
+ constructor(req, socket) {
502
+ super(socket);
503
+ this.method = req.method;
504
+ const url = req._url ??= new FastURL(req.url);
505
+ this.url = url.pathname + url.search;
506
+ for (const [key, value] of req.headers.entries()) this.headers[key.toLowerCase()] = value;
507
+ if (req.method !== "GET" && req.method !== "HEAD" && !this.headers["content-length"] && !this.headers["transfer-encoding"]) this.headers["transfer-encoding"] = "chunked";
508
+ const onData = (chunk) => {
509
+ this.push(chunk);
510
+ };
511
+ socket.on("data", onData);
512
+ socket.once("end", () => {
513
+ this.emit("end");
514
+ this.off("data", onData);
515
+ });
516
+ }
517
+ };
518
+ function callNodeHandler(handler, req) {
519
+ const isMiddleware = handler.length > 2;
520
+ const nodeCtx = req.runtime?.node;
521
+ if (!nodeCtx || !nodeCtx.req || !nodeCtx.res) throw new Error("Node.js runtime context is not available.");
522
+ const { req: nodeReq, res: nodeRes } = nodeCtx;
523
+ let _headers;
524
+ const webRes = new NodeResponse(void 0, {
525
+ get status() {
526
+ return nodeRes.statusCode;
527
+ },
528
+ get statusText() {
529
+ return nodeRes.statusMessage;
530
+ },
531
+ get headers() {
532
+ if (!_headers) {
533
+ const headerEntries = [];
534
+ const rawHeaders = nodeRes.getHeaders();
535
+ for (const [name, value] of Object.entries(rawHeaders)) if (Array.isArray(value)) for (const v of value) headerEntries.push([name, v]);
536
+ else if (value) headerEntries.push([name, String(value)]);
537
+ _headers = new Headers(headerEntries);
538
+ }
539
+ return _headers;
540
+ }
541
+ });
542
+ return new Promise((resolve, reject) => {
543
+ nodeRes.once("close", () => resolve(webRes));
544
+ nodeRes.once("finish", () => resolve(webRes));
545
+ nodeRes.once("error", (error) => reject(error));
546
+ let streamPromise;
547
+ nodeRes.once("pipe", (stream) => {
548
+ streamPromise = new Promise((resolve) => {
549
+ stream.once("end", () => resolve(webRes));
550
+ stream.once("error", (error) => reject(error));
551
+ });
552
+ });
553
+ try {
554
+ if (isMiddleware) Promise.resolve(handler(nodeReq, nodeRes, (error) => error ? reject(error) : streamPromise || resolve(webRes))).catch((error) => reject(error));
555
+ else Promise.resolve(handler(nodeReq, nodeRes)).then(() => streamPromise || webRes);
556
+ } catch (error) {
557
+ reject(error);
558
+ }
559
+ });
560
+ }
452
561
  var WebServerResponse = class extends ServerResponse {
453
562
  #socket;
454
563
  constructor(req, socket) {
@@ -487,20 +596,6 @@ var WebServerResponse = class extends ServerResponse {
487
596
  });
488
597
  }
489
598
  };
490
-
491
- //#endregion
492
- //#region src/adapters/_node/web/fetch.ts
493
- /**
494
- * Calls a Node.js HTTP Request handler with a Fetch API Request object and returns a Response object.
495
- *
496
- * If the web Request contains an existing Node.js req/res pair (indicating it originated from a Node.js server from srvx/node), it will be called directly.
497
- *
498
- * Otherwise, new Node.js IncomingMessage and ServerResponse objects are created and linked to a custom Duplex stream that bridges the Fetch API streams with Node.js streams.
499
- *
500
- * The handler is invoked with these objects, and the response is constructed from the ServerResponse once it is finished.
501
- *
502
- * @experimental Behavior might be unstable.
503
- */
504
599
  async function fetchNodeHandler(handler, req) {
505
600
  const nodeRuntime = req.runtime?.node;
506
601
  if (nodeRuntime && nodeRuntime.req && nodeRuntime.res) return await callNodeHandler(handler, req);
@@ -521,12 +616,6 @@ async function fetchNodeHandler(handler, req) {
521
616
  });
522
617
  }
523
618
  }
524
-
525
- //#endregion
526
- //#region src/adapters/_node/adapter.ts
527
- /**
528
- * Converts a Fetch API handler to a Node.js HTTP handler.
529
- */
530
619
  function toNodeHandler(handler) {
531
620
  if (handler.__nodeHandler) return handler.__nodeHandler;
532
621
  function convertedNodeHandler(nodeReq, nodeRes) {
@@ -540,11 +629,6 @@ function toNodeHandler(handler) {
540
629
  assignFnName(convertedNodeHandler, handler, " (converted to Node handler)");
541
630
  return convertedNodeHandler;
542
631
  }
543
- /**
544
- * Converts a Node.js HTTP handler into a Fetch API handler.
545
- *
546
- * @experimental Behavior might be unstable and won't work in Bun and Deno currently (tracker: https://github.com/h3js/srvx/issues/132)
547
- */
548
632
  function toFetchHandler(handler) {
549
633
  if (handler.__fetchHandler) return handler.__fetchHandler;
550
634
  function convertedNodeHandler(req) {
@@ -559,9 +643,6 @@ function assignFnName(target, source, suffix) {
559
643
  Object.defineProperty(target, "name", { value: `${source.name}${suffix}` });
560
644
  } catch {}
561
645
  }
562
-
563
- //#endregion
564
- //#region src/adapters/node.ts
565
646
  function serve(options) {
566
647
  return new NodeServer(options);
567
648
  }
@@ -641,12 +722,10 @@ var NodeServer = class {
641
722
  async close(closeAll) {
642
723
  await Promise.all([this.#wait.wait(), new Promise((resolve, reject) => {
643
724
  const server = this.node?.server;
644
- if (!server) return resolve();
725
+ if (!server || !server.listening) return resolve();
645
726
  if (closeAll && "closeAllConnections" in server) server.closeAllConnections();
646
727
  server.close((error) => error ? reject(error) : resolve());
647
728
  })]);
648
729
  }
649
730
  };
650
-
651
- //#endregion
652
- export { NodeResponse as FastResponse, NodeResponse, FastURL, NodeRequest, fetchNodeHandler, patchGlobalRequest, sendNodeResponse, serve, toFetchHandler, toNodeHandler };
731
+ export { NodeResponse as FastResponse, NodeResponse, FastURL, NodeRequest, fetchNodeHandler, patchGlobalRequest, sendNodeResponse, serve, toFetchHandler, toNodeHandler };
@@ -1,4 +1,4 @@
1
- import { g as Server, x as ServerRequest, y as ServerOptions } from "../_chunks/types.mjs";
1
+ import { Server, ServerOptions, ServerRequest } from "../types.mjs";
2
2
 
3
3
  //#region src/adapters/service-worker.d.ts
4
4
  declare const FastURL: typeof globalThis.URL;
@@ -1,10 +1,8 @@
1
1
  import { r as wrapFetch, t as errorPlugin } from "../_chunks/_plugins.mjs";
2
-
3
- //#region src/adapters/service-worker.ts
4
2
  const FastURL = URL;
5
3
  const FastResponse = Response;
6
4
  const isBrowserWindow = typeof window !== "undefined" && typeof navigator !== "undefined";
7
- const isServiceWorker = /* @__PURE__ */ (() => typeof self !== "undefined" && "skipWaiting" in self)();
5
+ const isServiceWorker = typeof self !== "undefined" && "skipWaiting" in self;
8
6
  function serve(options) {
9
7
  return new ServiceWorkerServer(options);
10
8
  }
@@ -75,6 +73,4 @@ var ServiceWorkerServer = class {
75
73
  } else if (isServiceWorker) await self.registration.unregister();
76
74
  }
77
75
  };
78
-
79
- //#endregion
80
- export { FastResponse, FastURL, serve };
76
+ export { FastResponse, FastURL, serve };
package/dist/cli.d.mts CHANGED
@@ -1,17 +1,52 @@
1
+ import { t as LoadOptions } from "./_chunks/loader.mjs";
1
2
  import { Server } from "srvx";
2
3
 
3
- //#region src/cli.d.ts
4
- declare function main(mainOpts: MainOpts): Promise<void>;
4
+ //#region src/cli/types.d.ts
5
5
  declare global {
6
- var __srvx_version__: string | undefined;
7
- var __srvx__: Server;
8
- var __srvx_listen_cb__: () => void;
6
+ var __srvx__: Server | undefined;
9
7
  }
10
- type MainOpts = {
11
- command: string;
12
- docs: string;
13
- issues: string;
8
+ type MainOptions = CLIOptions & {
9
+ args?: string[];
10
+ usage?: {
11
+ command?: string;
12
+ docs?: string;
13
+ issues?: string;
14
+ };
14
15
  };
15
- declare function usage(mainOpts: MainOpts): string;
16
+ /**
17
+ * CLI options for srvx command
18
+ */
19
+ type CLIOptions = {
20
+ /** CLI mode: "serve" to start a server, "fetch" to make HTTP requests */mode?: "serve" | "fetch"; /** Show help message */
21
+ help?: boolean; /** Show server and runtime versions */
22
+ version?: boolean; /** Working directory for resolving entry file */
23
+ dir?: string; /** Server entry file to use */
24
+ entry?: string; /** Run in production mode (no watch, no debug) */
25
+ prod?: boolean; /** Serve static files from the specified directory (default: "public") */
26
+ static?: string; /** ES module to preload */
27
+ import?: string; /** Host to bind to (default: all interfaces) */
28
+ hostname?: string; /** (alias to hostname) */
29
+ host?: string; /** Port to listen on (default: "3000") */
30
+ port?: string; /** Enable TLS (HTTPS/HTTP2) */
31
+ tls?: boolean; /** TLS certificate file */
32
+ cert?: string; /** TLS private key file */
33
+ key?: string; /** URL or path to fetch */
34
+ url?: string; /** HTTP method (default: "GET", or "POST" if body is provided) */
35
+ method?: string; /** Request headers (format: "Name: Value", can be used multiple times) */
36
+ header?: string[]; /** Show request and response headers */
37
+ verbose?: boolean; /** Request body (use "@-" for stdin, "@file" for file) */
38
+ data?: string;
39
+ };
40
+ //#endregion
41
+ //#region src/cli/main.d.ts
42
+ declare function main(mainOpts: MainOptions): Promise<void>;
43
+ //#endregion
44
+ //#region src/cli/fetch.d.ts
45
+ declare function cliFetch(cliOpts: CLIOptions & LoadOptions & {
46
+ loader?: LoadOptions;
47
+ stdin?: typeof process.stdin;
48
+ stdout?: typeof process.stdout;
49
+ stderr?: typeof process.stderr;
50
+ }): Promise<Response>;
16
51
  //#endregion
17
- export { main, usage };
52
+ export { type CLIOptions, type MainOptions, cliFetch, main };