srvx 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -9,9 +9,9 @@
9
9
 
10
10
  Universal Server API based on web platform standards. Works with [Deno](https://deno.com/), [Bun](https://bun.sh/) and [Node.js](https://nodejs.org/en).
11
11
 
12
- - ✅ Seamless runtime integration with identical usage ([handler](https://srvx.unjs.io/guide/handler) and [instance](https://srvx.unjs.io/guide/server))
12
+ - ✅ Seamless runtime integration with identical usage ([handler](https://srvx.h3.dev/guide/handler) and [instance](https://srvx.h3.dev/guide/server))
13
13
  - ✅ Zero overhead [Deno](https://deno.com/) and [Bun](https://bun.sh/) support
14
- - ✅ [Node.js compatibility](https://srvx.unjs.io/guide/node) with ~native perf and [fast response](https://srvx.unjs.io/guide/node#fast-response) support
14
+ - ✅ [Node.js compatibility](https://srvx.h3.dev/guide/node) with ~native perf and [fast response](https://srvx.h3.dev/guide/node#fast-response) support
15
15
 
16
16
  ## Quick start
17
17
 
@@ -24,13 +24,9 @@ const server = serve({
24
24
  return new Response("👋 Hello there!");
25
25
  },
26
26
  });
27
-
28
- await server.ready();
29
-
30
- console.log(`🚀 Server ready at ${server.url}`);
31
27
  ```
32
28
 
33
- 👉 **Visit the 📖 [Documentation](https://srvx.unjs.io/) to learn more.**
29
+ 👉 **Visit the 📖 [Documentation](https://srvx.h3.dev/) to learn more.**
34
30
 
35
31
  ## Development
36
32
 
@@ -50,11 +46,11 @@ console.log(`🚀 Server ready at ${server.url}`);
50
46
 
51
47
  <!-- automd:contributors author=pi0 license=MIT -->
52
48
 
53
- Published under the [MIT](https://github.com/unjs/srvx/blob/main/LICENSE) license.
54
- Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/srvx/graphs/contributors) 💛
49
+ Published under the [MIT](https://github.com/h3js/srvx/blob/main/LICENSE) license.
50
+ Made by [@pi0](https://github.com/pi0) and [community](https://github.com/h3js/srvx/graphs/contributors) 💛
55
51
  <br><br>
56
- <a href="https://github.com/unjs/srvx/graphs/contributors">
57
- <img src="https://contrib.rocks/image?repo=unjs/srvx" />
52
+ <a href="https://github.com/h3js/srvx/graphs/contributors">
53
+ <img src="https://contrib.rocks/image?repo=h3js/srvx" />
58
54
  </a>
59
55
 
60
56
  <!-- /automd -->
@@ -5,13 +5,7 @@ import 'node:https';
5
5
  import 'node:net';
6
6
  import '@cloudflare/workers-types';
7
7
 
8
- declare const Response: {
9
- new (body?: BodyInit | null, init?: ResponseInit): Response;
10
- prototype: Response;
11
- error(): Response;
12
- json(data: any, init?: ResponseInit): Response;
13
- redirect(url: string | URL, status?: number): Response;
14
- };
8
+ declare const Response: typeof globalThis.Response;
15
9
  declare function serve(options: ServerOptions): BunServer;
16
10
  declare class BunServer implements Server<BunFetchHandler> {
17
11
  readonly runtime = "bun";
@@ -20,10 +14,10 @@ declare class BunServer implements Server<BunFetchHandler> {
20
14
  readonly serveOptions: Bun.ServeOptions | Bun.TLSServeOptions;
21
15
  readonly fetch: BunFetchHandler;
22
16
  constructor(options: ServerOptions);
23
- serve(): Promise<Awaited<this>>;
17
+ serve(): Promise<this>;
24
18
  get url(): string | undefined;
25
- ready(): Promise<Awaited<this>>;
26
- close(closeAll?: boolean): Promise<void | undefined>;
19
+ ready(): Promise<this>;
20
+ close(closeAll?: boolean): Promise<void>;
27
21
  }
28
22
 
29
23
  export { Response, serve };
@@ -1,4 +1,4 @@
1
- import { r as resolveTLSOptions, a as resolvePort } from '../shared/srvx.lC_d9z2b.mjs';
1
+ import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
2
2
  import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
3
3
  import 'node:fs';
4
4
 
@@ -29,9 +29,9 @@ class BunServer {
29
29
  };
30
30
  const tls = resolveTLSOptions(this.options);
31
31
  this.serveOptions = {
32
- hostname: this.options.hostname,
32
+ ...resolvePortAndHost(this.options),
33
33
  reusePort: this.options.reusePort,
34
- port: resolvePort(this.options.port, globalThis.process?.env.PORT),
34
+ error: this.options.onError,
35
35
  ...this.options.bun,
36
36
  tls: {
37
37
  cert: tls?.cert,
@@ -49,10 +49,23 @@ class BunServer {
49
49
  if (!this.bun.server) {
50
50
  this.bun.server = Bun.serve(this.serveOptions);
51
51
  }
52
+ printListening(this.options, this.url);
52
53
  return Promise.resolve(this);
53
54
  }
54
55
  get url() {
55
- return this.bun?.server?.url.href;
56
+ const server = this.bun?.server;
57
+ if (!server) {
58
+ return;
59
+ }
60
+ const address = server.address;
61
+ if (address) {
62
+ return fmtURL(
63
+ address.address,
64
+ address.port,
65
+ server.protocol === "https"
66
+ );
67
+ }
68
+ return server.url.href;
56
69
  }
57
70
  ready() {
58
71
  return Promise.resolve(this);
@@ -5,13 +5,7 @@ import 'node:https';
5
5
  import 'node:net';
6
6
  import 'bun';
7
7
 
8
- declare const Response: {
9
- new (body?: BodyInit | null, init?: ResponseInit): Response;
10
- prototype: Response;
11
- error(): Response;
12
- json(data: any, init?: ResponseInit): Response;
13
- redirect(url: string | URL, status?: number): Response;
14
- };
8
+ declare const Response: typeof globalThis.Response;
15
9
  declare function serve(options: ServerOptions): Server<CF.ExportedHandlerFetchHandler>;
16
10
 
17
11
  export { Response, serve };
@@ -1,4 +1,5 @@
1
1
  import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
2
+ import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
2
3
 
3
4
  const Response = globalThis.Response;
4
5
  function serve(options) {
@@ -10,7 +11,7 @@ class CloudflareServer {
10
11
  this.options = options;
11
12
  const fetchHandler = wrapFetch(
12
13
  this,
13
- this.options.fetch
14
+ wrapFetchOnError(this.options.fetch, this.options.onError)
14
15
  );
15
16
  this.fetch = (request, env, context) => {
16
17
  Object.defineProperties(request, {
@@ -5,13 +5,7 @@ import 'node:net';
5
5
  import 'bun';
6
6
  import '@cloudflare/workers-types';
7
7
 
8
- declare const Response: {
9
- new (body?: BodyInit | null, init?: ResponseInit): Response;
10
- prototype: Response;
11
- error(): Response;
12
- json(data: any, init?: ResponseInit): Response;
13
- redirect(url: string | URL, status?: number): Response;
14
- };
8
+ declare const Response: typeof globalThis.Response;
15
9
  declare function serve(options: ServerOptions): DenoServer;
16
10
  declare class DenoServer implements Server<DenoFetchHandler> {
17
11
  #private;
@@ -1,4 +1,4 @@
1
- import { r as resolveTLSOptions, a as resolvePort, f as fmtURL } from '../shared/srvx.lC_d9z2b.mjs';
1
+ import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
2
2
  import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
3
3
  import 'node:fs';
4
4
 
@@ -29,9 +29,9 @@ class DenoServer {
29
29
  };
30
30
  const tls = resolveTLSOptions(this.options);
31
31
  this.serveOptions = {
32
- port: resolvePort(this.options.port, globalThis.Deno?.env.get("PORT")),
33
- hostname: this.options.hostname,
32
+ ...resolvePortAndHost(this.options),
34
33
  reusePort: this.options.reusePort,
34
+ onError: this.options.onError,
35
35
  ...tls ? { key: tls.key, cert: tls.cert, passphrase: tls.passphrase } : {},
36
36
  ...this.options.deno
37
37
  };
@@ -55,6 +55,7 @@ class DenoServer {
55
55
  if (this.options.deno?.onListen) {
56
56
  this.options.deno.onListen(info);
57
57
  }
58
+ printListening(this.options, this.url);
58
59
  onListenPromise.resolve();
59
60
  }
60
61
  },
@@ -0,0 +1,11 @@
1
+ import { ServerOptions, Server } from '../types.mjs';
2
+ import 'node:http';
3
+ import 'node:https';
4
+ import 'node:net';
5
+ import 'bun';
6
+ import '@cloudflare/workers-types';
7
+
8
+ declare const Response: typeof globalThis.Response;
9
+ declare function serve(options: ServerOptions): Server;
10
+
11
+ export { Response, serve };
@@ -0,0 +1,30 @@
1
+ import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
2
+ import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
3
+
4
+ const Response = globalThis.Response;
5
+ function serve(options) {
6
+ return new GenericServer(options);
7
+ }
8
+ class GenericServer {
9
+ constructor(options) {
10
+ this.runtime = "generic";
11
+ this.options = options;
12
+ const fetchHandler = wrapFetch(
13
+ this,
14
+ wrapFetchOnError(this.options.fetch, this.options.onError)
15
+ );
16
+ this.fetch = (request) => {
17
+ return Promise.resolve(fetchHandler(request));
18
+ };
19
+ }
20
+ serve() {
21
+ }
22
+ ready() {
23
+ return Promise.resolve(this);
24
+ }
25
+ close() {
26
+ return Promise.resolve();
27
+ }
28
+ }
29
+
30
+ export { Response, serve };
@@ -13,44 +13,14 @@ type NodeResponse = InstanceType<typeof NodeResponse>;
13
13
  * It is faster because in most cases it doesn't create a full Response instance.
14
14
  */
15
15
  declare const NodeResponse: {
16
- new (body?: BodyInit | null, init?: ResponseInit): {
17
- "__#4366@#body"?: BodyInit | null;
18
- "__#4366@#init"?: ResponseInit;
19
- /**
20
- * Prepare Node.js response object
21
- */
22
- nodeResponse(): {
16
+ new (body?: BodyInit | null, init?: ResponseInit): globalThis.Response & {
17
+ readonly nodeResponse: () => {
23
18
  status: number;
24
19
  statusText: string;
25
20
  headers: NodeHttp__default.OutgoingHttpHeader[];
26
- body: string | Buffer<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | ReadableStream<Uint8Array<ArrayBufferLike>> | Readable | DataView<ArrayBufferLike> | null | undefined;
21
+ body: string | Buffer | Uint8Array | DataView | ReadableStream<Uint8Array> | Readable | undefined | null;
27
22
  };
28
- /** Lazy initialized response instance */
29
- "__#4366@#responseObj"?: globalThis.Response;
30
- /** Lazy initialized headers instance */
31
- "__#4366@#headersObj"?: Headers;
32
- clone(): globalThis.Response;
33
- readonly "__#4366@#response": globalThis.Response;
34
- readonly headers: Headers;
35
- readonly ok: boolean;
36
- readonly redirected: boolean;
37
- readonly status: number;
38
- readonly statusText: string;
39
- readonly type: ResponseType;
40
- readonly url: string;
41
- "__#4366@#fastBody"<T extends object>(as: new (...args: any[]) => T): T | null | false;
42
- readonly body: ReadableStream<Uint8Array> | null;
43
- readonly bodyUsed: boolean;
44
- arrayBuffer(): Promise<ArrayBuffer>;
45
- blob(): Promise<Blob>;
46
- bytes(): Promise<Uint8Array>;
47
- formData(): Promise<FormData>;
48
- text(): Promise<string>;
49
- json(): Promise<any>;
50
23
  };
51
- json(data: any, init?: ResponseInit): globalThis.Response;
52
- error(): globalThis.Response;
53
- redirect(url: string | URL, status?: number): globalThis.Response;
54
24
  };
55
25
 
56
26
  declare const NodeRequest: {
@@ -60,65 +30,17 @@ declare const NodeRequest: {
60
30
  }): ServerRequest;
61
31
  };
62
32
 
63
- declare const kNodeInspect: unique symbol;
64
-
65
33
  declare const NodeRequestHeaders: {
66
34
  new (nodeCtx: {
67
35
  req: NodeHttp__default.IncomingMessage;
68
36
  res?: NodeHttp__default.ServerResponse;
69
- }): {
70
- _node: {
71
- req: NodeHttp__default.IncomingMessage;
72
- res?: NodeHttp__default.ServerResponse;
73
- };
74
- append(name: string, value: string): void;
75
- delete(name: string): void;
76
- get(name: string): string | null;
77
- getSetCookie(): string[];
78
- has(name: string): boolean;
79
- set(name: string, value: string): void;
80
- readonly count: number;
81
- getAll(_name: "set-cookie" | "Set-Cookie"): string[];
82
- toJSON(): Record<string, string>;
83
- forEach(cb: (value: string, key: string, parent: /*elided*/ any) => void, thisArg?: any): void;
84
- entries(): HeadersIterator<[string, string]>;
85
- keys(): HeadersIterator<string>;
86
- values(): HeadersIterator<string>;
87
- [Symbol.iterator](): HeadersIterator<[string, string]>;
88
- readonly [Symbol.toStringTag]: string;
89
- [kNodeInspect](): {
90
- [k: string]: string;
91
- };
92
- };
37
+ }): globalThis.Headers;
93
38
  };
94
39
  declare const NodeResponseHeaders: {
95
40
  new (nodeCtx: {
96
41
  req?: NodeHttp__default.IncomingMessage;
97
42
  res: NodeHttp__default.ServerResponse;
98
- }): {
99
- _node: {
100
- req?: NodeHttp__default.IncomingMessage;
101
- res: NodeHttp__default.ServerResponse;
102
- };
103
- append(name: string, value: string): void;
104
- delete(name: string): void;
105
- get(name: string): string | null;
106
- getSetCookie(): string[];
107
- has(name: string): boolean;
108
- set(name: string, value: string): void;
109
- readonly count: number;
110
- getAll(_name: "set-cookie" | "Set-Cookie"): string[];
111
- toJSON(): Record<string, string>;
112
- forEach(cb: (value: string, key: string, parent: /*elided*/ any) => void, thisArg?: any): void;
113
- entries(): HeadersIterator<[string, string]>;
114
- keys(): HeadersIterator<string>;
115
- values(): HeadersIterator<string>;
116
- [Symbol.iterator](): HeadersIterator<[string, string]>;
117
- readonly [Symbol.toStringTag]: string;
118
- [kNodeInspect](): {
119
- [k: string]: string;
120
- };
121
- };
43
+ }): globalThis.Headers;
122
44
  };
123
45
 
124
46
  declare function serve(options: ServerOptions): Server;
@@ -1,8 +1,9 @@
1
1
  import NodeHttp from 'node:http';
2
2
  import NodeHttps from 'node:https';
3
3
  import { splitSetCookieString } from 'cookie-es';
4
- import { r as resolveTLSOptions, a as resolvePort, f as fmtURL } from '../shared/srvx.lC_d9z2b.mjs';
4
+ import { r as resolveTLSOptions, a as resolvePortAndHost, p as printListening, f as fmtURL } from '../shared/srvx.FQfHxe2J.mjs';
5
5
  import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
6
+ import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
6
7
  import 'node:fs';
7
8
 
8
9
  async function sendNodeResponse(nodeRes, webRes) {
@@ -906,7 +907,10 @@ class NodeServer {
906
907
  constructor(options) {
907
908
  this.runtime = "node";
908
909
  this.options = options;
909
- const fetchHandler = wrapFetch(this, this.options.fetch);
910
+ const fetchHandler = wrapFetch(
911
+ this,
912
+ wrapFetchOnError(this.options.fetch, this.options.onError)
913
+ );
910
914
  this.fetch = fetchHandler;
911
915
  const handler = (nodeReq, nodeRes) => {
912
916
  const request = new NodeRequest({ req: nodeReq, res: nodeRes });
@@ -914,9 +918,10 @@ class NodeServer {
914
918
  return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
915
919
  };
916
920
  const tls = resolveTLSOptions(this.options);
921
+ const { port, hostname: host } = resolvePortAndHost(this.options);
917
922
  this.serveOptions = {
918
- port: resolvePort(this.options.port, globalThis.process?.env.PORT),
919
- host: this.options.hostname,
923
+ port,
924
+ host,
920
925
  exclusive: !this.options.reusePort,
921
926
  ...tls ? { cert: tls.cert, key: tls.key, passphrase: tls.passphrase } : {},
922
927
  ...this.options.node
@@ -936,7 +941,10 @@ class NodeServer {
936
941
  return Promise.resolve(this.#listeningPromise).then(() => this);
937
942
  }
938
943
  this.#listeningPromise = new Promise((resolve) => {
939
- this.node.server.listen(this.serveOptions, () => resolve());
944
+ this.node.server.listen(this.serveOptions, () => {
945
+ printListening(this.options, this.url);
946
+ resolve();
947
+ });
940
948
  });
941
949
  }
942
950
  get url() {
@@ -0,0 +1,13 @@
1
+ import { ServerRequest, ServerOptions, Server } from '../types.mjs';
2
+ import 'node:http';
3
+ import 'node:https';
4
+ import 'node:net';
5
+ import 'bun';
6
+ import '@cloudflare/workers-types';
7
+
8
+ declare const Response: typeof globalThis.Response;
9
+ type ServiceWorkerHandler = (request: ServerRequest, event: FetchEvent) => Response | Promise<Response>;
10
+ declare function serve(options: ServerOptions): Server<ServiceWorkerHandler>;
11
+
12
+ export { Response, serve };
13
+ export type { ServiceWorkerHandler };
@@ -0,0 +1,87 @@
1
+ import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
2
+ import { w as wrapFetchOnError } from '../shared/srvx.BD9sRkkl.mjs';
3
+
4
+ const Response = globalThis.Response;
5
+ const isMainThread = () => typeof navigator !== "undefined" && "serviceWorker" in navigator;
6
+ function serve(options) {
7
+ return new ServiceWorkerServer(options);
8
+ }
9
+ class ServiceWorkerServer {
10
+ constructor(options) {
11
+ this.runtime = "service-worker";
12
+ this.options = options;
13
+ const fetchHandler = wrapFetch(
14
+ this,
15
+ wrapFetchOnError(this.options.fetch, this.options.onError)
16
+ );
17
+ this.fetch = (request, event) => {
18
+ Object.defineProperties(request, {
19
+ runtime: {
20
+ enumerable: true,
21
+ value: { runtime: "service-worker", serviceWorker: { event } }
22
+ }
23
+ });
24
+ return Promise.resolve(fetchHandler(request));
25
+ };
26
+ if (!options.manual) {
27
+ this.serve();
28
+ }
29
+ }
30
+ #fetchListener;
31
+ #listeningPromise;
32
+ serve() {
33
+ if (isMainThread()) {
34
+ const swURL = this.options.serviceWorker?.url;
35
+ if (!swURL) {
36
+ throw new Error(
37
+ "Service worker URL is not provided. Please set the `serviceWorker.url` serve option or manually register."
38
+ );
39
+ }
40
+ this.#listeningPromise = navigator.serviceWorker.register(swURL, {
41
+ type: "module",
42
+ scope: this.options.serviceWorker?.scope
43
+ }).then((registration) => {
44
+ registration.addEventListener("updatefound", () => {
45
+ location.replace(location.href);
46
+ });
47
+ });
48
+ return;
49
+ }
50
+ this.#fetchListener = async (event) => {
51
+ if (/\/[^/]*\.[a-zA-Z0-9]+$/.test(new URL(event.request.url).pathname)) {
52
+ return;
53
+ }
54
+ const response = await this.fetch(event.request, event);
55
+ if (response.status !== 404) {
56
+ event.respondWith(response);
57
+ }
58
+ };
59
+ addEventListener("fetch", this.#fetchListener);
60
+ globalThis.addEventListener("install", () => {
61
+ globalThis.skipWaiting();
62
+ });
63
+ globalThis.addEventListener("activate", (event) => {
64
+ event.waitUntil(globalThis.clients?.claim?.());
65
+ });
66
+ }
67
+ ready() {
68
+ return Promise.resolve(this.#listeningPromise).then(() => this);
69
+ }
70
+ async close() {
71
+ if (this.#fetchListener) {
72
+ removeEventListener("fetch", this.#fetchListener);
73
+ }
74
+ if (isMainThread()) {
75
+ const registrations = await navigator.serviceWorker.getRegistrations();
76
+ for (const registration of registrations) {
77
+ if (registration.active) {
78
+ await registration.unregister();
79
+ }
80
+ }
81
+ } else {
82
+ await globalThis.registration.unregister();
83
+ }
84
+ }
85
+ }
86
+
87
+ export { Response, serve };
@@ -0,0 +1,16 @@
1
+ function wrapFetchOnError(fetchHandler, onError) {
2
+ if (!onError) return fetchHandler;
3
+ return (...params) => {
4
+ try {
5
+ const result = fetchHandler(...params);
6
+ if (result instanceof Promise) {
7
+ return result.catch(onError);
8
+ }
9
+ return result;
10
+ } catch (error) {
11
+ return onError(error);
12
+ }
13
+ };
14
+ }
15
+
16
+ export { wrapFetchOnError as w };
@@ -1,11 +1,10 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
 
3
- function resolvePort(portOptions, portEnv) {
4
- const portInput = portOptions ?? portEnv;
5
- if (portInput === void 0) {
6
- return 3e3;
7
- }
8
- return typeof portInput === "number" ? portInput : Number.parseInt(portInput, 10);
3
+ function resolvePortAndHost(opts) {
4
+ const _port = opts.port ?? globalThis.process?.env.PORT ?? 3e3;
5
+ const port = typeof _port === "number" ? _port : Number.parseInt(_port, 10);
6
+ const hostname = opts.hostname ?? globalThis.process?.env.HOST;
7
+ return { port, hostname };
9
8
  }
10
9
  function fmtURL(host, port, secure) {
11
10
  if (!host || !port) {
@@ -16,6 +15,25 @@ function fmtURL(host, port, secure) {
16
15
  }
17
16
  return `http${secure ? "s" : ""}://${host}:${port}/`;
18
17
  }
18
+ function printListening(opts, url) {
19
+ if (!url || (opts.silent ?? globalThis.process?.env?.TEST)) {
20
+ return;
21
+ }
22
+ const _url = new URL(url);
23
+ const allInterfaces = _url.hostname === "[::]" || _url.hostname === "0.0.0.0";
24
+ if (allInterfaces) {
25
+ _url.hostname = "localhost";
26
+ url = _url.href;
27
+ }
28
+ let listeningOn = `\u279C Listening on:`;
29
+ let additionalInfo = allInterfaces ? " (all interfaces)" : "";
30
+ if (globalThis.process.stdout?.isTTY) {
31
+ listeningOn = `\x1B[32m${listeningOn}\x1B[0m`;
32
+ url = `\x1B[36m${url}\x1B[0m`;
33
+ additionalInfo = `\x1B[2m${additionalInfo}\x1B[0m`;
34
+ }
35
+ console.log(` ${listeningOn} ${url}${additionalInfo}`);
36
+ }
19
37
  function resolveTLSOptions(opts) {
20
38
  if (!opts.tls || opts.protocol === "http") {
21
39
  return;
@@ -54,4 +72,4 @@ function resolveCertOrKey(value) {
54
72
  return readFileSync(value, "utf8");
55
73
  }
56
74
 
57
- export { resolvePort as a, fmtURL as f, resolveTLSOptions as r };
75
+ export { resolvePortAndHost as a, fmtURL as f, printListening as p, resolveTLSOptions as r };
package/dist/types.d.mts CHANGED
@@ -60,6 +60,10 @@ interface ServerOptions {
60
60
  * 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.
61
61
  */
62
62
  protocol?: "http" | "https";
63
+ /**
64
+ * If set to `true`, server will not print the listening address.
65
+ */
66
+ silent?: boolean;
63
67
  /**
64
68
  * TLS server options.
65
69
  */
@@ -77,6 +81,12 @@ interface ServerOptions {
77
81
  */
78
82
  passphrase?: string;
79
83
  };
84
+ /**
85
+ * Runtime agnostic error handler (optional).
86
+ *
87
+ * @note This handler will take precedence over runtime specific error handlers.
88
+ */
89
+ onError?: ErrorHandler;
80
90
  /**
81
91
  * Node.js server options.
82
92
  */
@@ -93,12 +103,26 @@ interface ServerOptions {
93
103
  * @docs https://docs.deno.com/api/deno/~/Deno.serve
94
104
  */
95
105
  deno?: Deno.ServeOptions;
106
+ /**
107
+ * Service worker options
108
+ */
109
+ serviceWorker?: {
110
+ /**
111
+ * The path to the service worker file to be registered.
112
+ */
113
+ url?: string;
114
+ /**
115
+ * The scope of the service worker.
116
+ *
117
+ */
118
+ scope?: string;
119
+ };
96
120
  }
97
121
  interface Server<Handler = ServerHandler> {
98
122
  /**
99
123
  * Current runtime name
100
124
  */
101
- readonly runtime: "node" | "deno" | "bun" | "cloudflare";
125
+ readonly runtime: "node" | "deno" | "bun" | "cloudflare" | "service-worker" | "generic";
102
126
  /**
103
127
  * Server options
104
128
  */
@@ -201,9 +225,11 @@ interface ServerRequest extends Request {
201
225
  ip?: string | undefined;
202
226
  }
203
227
  type FetchHandler = (request: Request) => Response | Promise<Response>;
228
+ type ErrorHandler = (error: unknown) => Response | Promise<Response>;
204
229
  type BunFetchHandler = (request: Request, server?: Bun.Server) => Response | Promise<Response>;
205
230
  type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
206
231
  type NodeHttpHandler = (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
207
232
  type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
208
233
 
209
- export { type BunFetchHandler, type CloudflareFetchHandler, type DenoFetchHandler, type FetchHandler, type NodeHttpHandler, Response, type Server, type ServerHandler, type ServerOptions, type ServerPlugin, type ServerPluginInstance, type ServerRequest, type ServerRuntimeContext, serve };
234
+ export { Response, serve };
235
+ export type { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FetchHandler, NodeHttpHandler, Server, ServerHandler, ServerOptions, ServerPlugin, ServerPluginInstance, ServerRequest, ServerRuntimeContext };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "srvx",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Universal Server API based on web platform standards. Works seamlessly with Deno, Bun and Node.js.",
5
- "repository": "unjs/srvx",
5
+ "repository": "h3js/srvx",
6
+ "homepage": "https://srvx.h3.dev",
6
7
  "license": "MIT",
7
8
  "sideEffects": false,
8
9
  "type": "module",
@@ -12,12 +13,16 @@
12
13
  "./bun": "./dist/adapters/bun.mjs",
13
14
  "./node": "./dist/adapters/node.mjs",
14
15
  "./cloudflare": "./dist/adapters/cloudflare.mjs",
16
+ "./generic": "./dist/adapters/generic.mjs",
17
+ "./service-worker": "./dist/adapters/service-worker.mjs",
15
18
  ".": {
16
19
  "types": "./dist/types.d.mts",
17
20
  "deno": "./dist/adapters/deno.mjs",
18
21
  "bun": "./dist/adapters/bun.mjs",
19
22
  "workerd": "./dist/adapters/cloudflare.mjs",
20
- "node": "./dist/adapters/node.mjs"
23
+ "browser": "./dist/adapters/service-worker.mjs",
24
+ "node": "./dist/adapters/node.mjs",
25
+ "default": "./dist/adapters/generic.mjs"
21
26
  }
22
27
  },
23
28
  "types": "./dist/types.d.mts",
@@ -36,6 +41,7 @@
36
41
  "play:deno": "deno run -A playground/app.mjs",
37
42
  "play:mkcert": "openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj /CN=srvx.local",
38
43
  "play:node": "node playground/app.mjs",
44
+ "play:sw": "pnpm build && pnpx serve playground",
39
45
  "release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
40
46
  "test": "pnpm lint && pnpm test:types && vitest run --coverage",
41
47
  "test:types": "tsc --noEmit --skipLibCheck"
@@ -47,26 +53,26 @@
47
53
  "cookie-es": "^2.0.0"
48
54
  },
49
55
  "devDependencies": {
50
- "@cloudflare/workers-types": "^4.20250321.0",
51
- "@hono/node-server": "^1.14.0",
56
+ "@cloudflare/workers-types": "^4.20250423.0",
57
+ "@hono/node-server": "^1.14.1",
52
58
  "@mjackson/node-fetch-server": "^0.6.1",
53
- "@types/bun": "^1.2.5",
59
+ "@types/bun": "^1.2.10",
54
60
  "@types/deno": "^2.2.0",
55
- "@types/node": "^22.13.13",
56
- "@vitest/coverage-v8": "^3.0.9",
61
+ "@types/node": "^22.14.1",
62
+ "@vitest/coverage-v8": "^3.1.2",
57
63
  "automd": "^0.4.0",
58
64
  "changelogen": "^0.6.1",
59
- "eslint": "^9.23.0",
65
+ "eslint": "^9.25.1",
60
66
  "eslint-config-unjs": "^0.4.2",
61
67
  "execa": "^9.5.2",
62
68
  "get-port-please": "^3.1.2",
63
69
  "jiti": "^2.4.2",
64
70
  "prettier": "^3.5.3",
65
- "typescript": "^5.8.2",
71
+ "typescript": "^5.8.3",
66
72
  "unbuild": "^3.5.0",
67
- "vitest": "^3.0.9"
73
+ "vitest": "^3.1.2"
68
74
  },
69
- "packageManager": "pnpm@10.6.5",
75
+ "packageManager": "pnpm@10.9.0",
70
76
  "engines": {
71
77
  "node": ">=20.11.1"
72
78
  }