srvx 0.2.5 → 0.2.6
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 +1 -1
- package/dist/adapters/bun.d.mts +2 -1
- package/dist/adapters/bun.mjs +9 -1
- package/dist/adapters/cloudflare.d.mts +1 -0
- package/dist/adapters/deno.d.mts +2 -1
- package/dist/adapters/deno.mjs +9 -2
- package/dist/adapters/node.d.mts +7 -6
- package/dist/adapters/node.mjs +21 -5
- package/dist/shared/srvx.lC_d9z2b.mjs +57 -0
- package/dist/types.d.mts +28 -2
- package/package.json +10 -9
- package/dist/shared/srvx.PbkQy9Ck.mjs +0 -18
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
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
|
|
12
|
+
- ✅ Seamless runtime integration with identical usage ([handler](https://srvx.unjs.io/guide/handler) and [instance](https://srvx.unjs.io/guide/server))
|
|
13
13
|
- ✅ Zero overhead [Deno](https://deno.com/) and [Bun](https://bun.sh/) support
|
|
14
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
|
|
15
15
|
|
package/dist/adapters/bun.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ServerOptions, Server, BunFetchandler } from '../types.mjs';
|
|
2
2
|
import * as Bun from 'bun';
|
|
3
3
|
import 'node:http';
|
|
4
|
+
import 'node:https';
|
|
4
5
|
import 'node:net';
|
|
5
6
|
import '@cloudflare/workers-types';
|
|
6
7
|
|
|
@@ -16,7 +17,7 @@ declare class BunServer implements Server<BunFetchandler> {
|
|
|
16
17
|
readonly runtime = "bun";
|
|
17
18
|
readonly options: ServerOptions;
|
|
18
19
|
readonly bun: Server["bun"];
|
|
19
|
-
readonly serveOptions: Bun.ServeOptions;
|
|
20
|
+
readonly serveOptions: Bun.ServeOptions | Bun.TLSServeOptions;
|
|
20
21
|
readonly fetch: BunFetchandler;
|
|
21
22
|
constructor(options: ServerOptions);
|
|
22
23
|
serve(): Promise<Awaited<this>>;
|
package/dist/adapters/bun.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { r as resolvePort } from '../shared/srvx.
|
|
1
|
+
import { r as resolveTLSOptions, a as resolvePort } from '../shared/srvx.lC_d9z2b.mjs';
|
|
2
2
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
3
|
+
import 'node:fs';
|
|
3
4
|
|
|
4
5
|
const Response = globalThis.Response;
|
|
5
6
|
function serve(options) {
|
|
@@ -21,11 +22,18 @@ class BunServer {
|
|
|
21
22
|
});
|
|
22
23
|
return fetchHandler(request);
|
|
23
24
|
};
|
|
25
|
+
const tls = resolveTLSOptions(this.options);
|
|
24
26
|
this.serveOptions = {
|
|
25
27
|
hostname: this.options.hostname,
|
|
26
28
|
reusePort: this.options.reusePort,
|
|
27
29
|
port: resolvePort(this.options.port, globalThis.process?.env.PORT),
|
|
28
30
|
...this.options.bun,
|
|
31
|
+
tls: {
|
|
32
|
+
cert: tls?.cert,
|
|
33
|
+
key: tls?.key,
|
|
34
|
+
passphrase: tls?.passphrase,
|
|
35
|
+
...this.options.bun?.tls
|
|
36
|
+
},
|
|
29
37
|
fetch: this.fetch
|
|
30
38
|
};
|
|
31
39
|
if (!options.manual) {
|
package/dist/adapters/deno.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ServerOptions, Server, DenoFetchHandler } from '../types.mjs';
|
|
2
2
|
import 'node:http';
|
|
3
|
+
import 'node:https';
|
|
3
4
|
import 'node:net';
|
|
4
5
|
import 'bun';
|
|
5
6
|
import '@cloudflare/workers-types';
|
|
@@ -17,7 +18,7 @@ declare class DenoServer implements Server<DenoFetchHandler> {
|
|
|
17
18
|
readonly runtime = "deno";
|
|
18
19
|
readonly options: ServerOptions;
|
|
19
20
|
readonly deno: Server["deno"];
|
|
20
|
-
readonly serveOptions: Deno.ServeTcpOptions;
|
|
21
|
+
readonly serveOptions: Deno.ServeTcpOptions | (Deno.ServeTcpOptions & Deno.TlsCertifiedKeyPem);
|
|
21
22
|
readonly fetch: DenoFetchHandler;
|
|
22
23
|
constructor(options: ServerOptions);
|
|
23
24
|
serve(): Promise<this>;
|
package/dist/adapters/deno.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { r as resolvePort, f as fmtURL } from '../shared/srvx.
|
|
1
|
+
import { r as resolveTLSOptions, a as resolvePort, f as fmtURL } from '../shared/srvx.lC_d9z2b.mjs';
|
|
2
2
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
3
|
+
import 'node:fs';
|
|
3
4
|
|
|
4
5
|
const Response = globalThis.Response;
|
|
5
6
|
function serve(options) {
|
|
@@ -21,10 +22,12 @@ class DenoServer {
|
|
|
21
22
|
});
|
|
22
23
|
return fetchHandler(request);
|
|
23
24
|
};
|
|
25
|
+
const tls = resolveTLSOptions(this.options);
|
|
24
26
|
this.serveOptions = {
|
|
25
27
|
port: resolvePort(this.options.port, globalThis.Deno?.env.get("PORT")),
|
|
26
28
|
hostname: this.options.hostname,
|
|
27
29
|
reusePort: this.options.reusePort,
|
|
30
|
+
...tls ? { key: tls.key, cert: tls.cert, passphrase: tls.passphrase } : {},
|
|
28
31
|
...this.options.deno
|
|
29
32
|
};
|
|
30
33
|
if (!options.manual) {
|
|
@@ -55,7 +58,11 @@ class DenoServer {
|
|
|
55
58
|
return Promise.resolve(this.#listeningPromise).then(() => this);
|
|
56
59
|
}
|
|
57
60
|
get url() {
|
|
58
|
-
return this.#listeningInfo ? fmtURL(
|
|
61
|
+
return this.#listeningInfo ? fmtURL(
|
|
62
|
+
this.#listeningInfo.hostname,
|
|
63
|
+
this.#listeningInfo.port,
|
|
64
|
+
!!this.serveOptions.cert
|
|
65
|
+
) : void 0;
|
|
59
66
|
}
|
|
60
67
|
ready() {
|
|
61
68
|
return Promise.resolve(this.#listeningPromise).then(() => this);
|
package/dist/adapters/node.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ServerOptions, Server, FetchHandler, NodeHttpHandler } from '../types.mjs';
|
|
2
2
|
import NodeHttp__default from 'node:http';
|
|
3
3
|
import { Readable } from 'node:stream';
|
|
4
|
+
import 'node:https';
|
|
4
5
|
import 'node:net';
|
|
5
6
|
import 'bun';
|
|
6
7
|
import '@cloudflare/workers-types';
|
|
@@ -13,8 +14,8 @@ type NodeFastResponse = InstanceType<typeof NodeFastResponse>;
|
|
|
13
14
|
*/
|
|
14
15
|
declare const NodeFastResponse: {
|
|
15
16
|
new (body?: BodyInit | null, init?: ResponseInit): {
|
|
16
|
-
"__#
|
|
17
|
-
"__#
|
|
17
|
+
"__#4363@#body"?: BodyInit | null;
|
|
18
|
+
"__#4363@#init"?: ResponseInit;
|
|
18
19
|
/**
|
|
19
20
|
* Prepare Node.js response object
|
|
20
21
|
*/
|
|
@@ -25,11 +26,11 @@ declare const NodeFastResponse: {
|
|
|
25
26
|
body: string | Uint8Array<ArrayBufferLike> | ReadableStream<Uint8Array<ArrayBufferLike>> | Readable | Buffer<ArrayBufferLike> | DataView<ArrayBufferLike> | null | undefined;
|
|
26
27
|
};
|
|
27
28
|
/** Lazy initialized response instance */
|
|
28
|
-
"__#
|
|
29
|
+
"__#4363@#responseObj"?: Response;
|
|
29
30
|
/** Lazy initialized headers instance */
|
|
30
|
-
"__#
|
|
31
|
+
"__#4363@#headersObj"?: Headers;
|
|
31
32
|
clone(): Response;
|
|
32
|
-
readonly "__#
|
|
33
|
+
readonly "__#4363@#response": Response;
|
|
33
34
|
readonly headers: Headers;
|
|
34
35
|
readonly ok: boolean;
|
|
35
36
|
readonly redirected: boolean;
|
|
@@ -37,7 +38,7 @@ declare const NodeFastResponse: {
|
|
|
37
38
|
readonly statusText: string;
|
|
38
39
|
readonly type: ResponseType;
|
|
39
40
|
readonly url: string;
|
|
40
|
-
"__#
|
|
41
|
+
"__#4363@#fastBody"<T extends object>(as: new (...args: any[]) => T): T | null | false;
|
|
41
42
|
readonly body: ReadableStream<Uint8Array> | null;
|
|
42
43
|
readonly bodyUsed: boolean;
|
|
43
44
|
arrayBuffer(): Promise<ArrayBuffer>;
|
package/dist/adapters/node.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import NodeHttp from 'node:http';
|
|
2
|
+
import NodeHttps from 'node:https';
|
|
2
3
|
import { splitSetCookieString } from 'cookie-es';
|
|
3
|
-
import { r as resolvePort, f as fmtURL } from '../shared/srvx.
|
|
4
|
+
import { r as resolveTLSOptions, a as resolvePort, f as fmtURL } from '../shared/srvx.lC_d9z2b.mjs';
|
|
4
5
|
import { w as wrapFetch } from '../shared/srvx.DhN4g5wJ.mjs';
|
|
6
|
+
import 'node:fs';
|
|
5
7
|
|
|
6
8
|
async function sendNodeResponse(nodeRes, webRes) {
|
|
7
9
|
if (!webRes) {
|
|
@@ -11,7 +13,7 @@ async function sendNodeResponse(nodeRes, webRes) {
|
|
|
11
13
|
if (webRes.nodeResponse) {
|
|
12
14
|
const res = webRes.nodeResponse();
|
|
13
15
|
if (!nodeRes.headersSent) {
|
|
14
|
-
nodeRes.writeHead(res.status, res.statusText, res.headers);
|
|
16
|
+
nodeRes.writeHead(res.status, res.statusText, res.headers.flat());
|
|
15
17
|
}
|
|
16
18
|
if (res.body) {
|
|
17
19
|
if (res.body instanceof ReadableStream) {
|
|
@@ -35,7 +37,11 @@ async function sendNodeResponse(nodeRes, webRes) {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
if (!nodeRes.headersSent) {
|
|
38
|
-
nodeRes.writeHead(
|
|
40
|
+
nodeRes.writeHead(
|
|
41
|
+
webRes.status || 200,
|
|
42
|
+
webRes.statusText,
|
|
43
|
+
headerEntries.flat()
|
|
44
|
+
);
|
|
39
45
|
}
|
|
40
46
|
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
|
|
41
47
|
}
|
|
@@ -416,6 +422,7 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
|
|
|
416
422
|
return false;
|
|
417
423
|
}
|
|
418
424
|
}
|
|
425
|
+
this.#hasBody = true;
|
|
419
426
|
return true;
|
|
420
427
|
}
|
|
421
428
|
get body() {
|
|
@@ -761,13 +768,18 @@ class NodeServer {
|
|
|
761
768
|
const res = fetchHandler(request);
|
|
762
769
|
return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
|
|
763
770
|
};
|
|
771
|
+
const tls = resolveTLSOptions(this.options);
|
|
764
772
|
this.serveOptions = {
|
|
765
773
|
port: resolvePort(this.options.port, globalThis.process?.env.PORT),
|
|
766
774
|
host: this.options.hostname,
|
|
767
775
|
exclusive: !this.options.reusePort,
|
|
776
|
+
...tls ? { cert: tls.cert, key: tls.key, passphrase: tls.passphrase } : {},
|
|
768
777
|
...this.options.node
|
|
769
778
|
};
|
|
770
|
-
const server =
|
|
779
|
+
const server = this.serveOptions.cert ? NodeHttps.createServer(
|
|
780
|
+
this.serveOptions,
|
|
781
|
+
handler
|
|
782
|
+
) : NodeHttp.createServer(this.serveOptions, handler);
|
|
771
783
|
this.node = { server, handler };
|
|
772
784
|
if (!options.manual) {
|
|
773
785
|
this.serve();
|
|
@@ -787,7 +799,11 @@ class NodeServer {
|
|
|
787
799
|
if (!addr) {
|
|
788
800
|
return;
|
|
789
801
|
}
|
|
790
|
-
return typeof addr === "string" ? addr : fmtURL(
|
|
802
|
+
return typeof addr === "string" ? addr : fmtURL(
|
|
803
|
+
addr.address,
|
|
804
|
+
addr.port,
|
|
805
|
+
this.node.server instanceof NodeHttps.Server
|
|
806
|
+
);
|
|
791
807
|
}
|
|
792
808
|
ready() {
|
|
793
809
|
return Promise.resolve(this.#listeningPromise).then(() => this);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
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);
|
|
9
|
+
}
|
|
10
|
+
function fmtURL(host, port, secure) {
|
|
11
|
+
if (!host || !port) {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
if (host.includes(":")) {
|
|
15
|
+
host = `[${host}]`;
|
|
16
|
+
}
|
|
17
|
+
return `http${secure ? "s" : ""}://${host}:${port}/`;
|
|
18
|
+
}
|
|
19
|
+
function resolveTLSOptions(opts) {
|
|
20
|
+
if (!opts.tls || opts.protocol === "http") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const cert = resolveCertOrKey(opts.tls.cert);
|
|
24
|
+
const key = resolveCertOrKey(opts.tls.key);
|
|
25
|
+
if (!cert && !key) {
|
|
26
|
+
if (opts.protocol === "https") {
|
|
27
|
+
throw new TypeError(
|
|
28
|
+
"TLS `cert` and `key` must be provided for `https` protocol."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!cert || !key) {
|
|
34
|
+
throw new TypeError("TLS `cert` and `key` must be provided together.");
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
cert,
|
|
38
|
+
key,
|
|
39
|
+
passphrase: opts.tls.passphrase
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function resolveCertOrKey(value) {
|
|
43
|
+
if (!value) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (typeof value !== "string") {
|
|
47
|
+
throw new TypeError(
|
|
48
|
+
"TLS certificate and key must be strings in PEM format or file paths."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (value.startsWith("-----BEGIN ")) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
return readFileSync(value, "utf8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { resolvePort as a, fmtURL as f, resolveTLSOptions as r };
|
package/dist/types.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as NodeHttp from 'node:http';
|
|
2
|
+
import * as NodeHttps from 'node:https';
|
|
2
3
|
import * as NodeNet from 'node:net';
|
|
3
4
|
import * as Bun from 'bun';
|
|
4
5
|
import * as CF from '@cloudflare/workers-types';
|
|
@@ -51,16 +52,41 @@ interface ServerOptions {
|
|
|
51
52
|
* **Note:** Despite Node.js built-in behavior that has `exclusive` flag (opposite of `reusePort`) enabled by default, srvx uses non-exclusive mode for consistency.
|
|
52
53
|
*/
|
|
53
54
|
reusePort?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The protocol to use for the server.
|
|
57
|
+
*
|
|
58
|
+
* Possible values are `http` and `https`.
|
|
59
|
+
*
|
|
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
|
+
*/
|
|
62
|
+
protocol?: "http" | "https";
|
|
63
|
+
/**
|
|
64
|
+
* TLS server options.
|
|
65
|
+
*/
|
|
66
|
+
tls?: {
|
|
67
|
+
/**
|
|
68
|
+
* File path or inlined TLS certificate in PEM format (required).
|
|
69
|
+
*/
|
|
70
|
+
cert?: string;
|
|
71
|
+
/**
|
|
72
|
+
* File path or inlined TLS private key in PEM format (required).
|
|
73
|
+
*/
|
|
74
|
+
key?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Passphrase for the private key (optional).
|
|
77
|
+
*/
|
|
78
|
+
passphrase?: string;
|
|
79
|
+
};
|
|
54
80
|
/**
|
|
55
81
|
* Node.js server options.
|
|
56
82
|
*/
|
|
57
|
-
node?: NodeHttp.ServerOptions & NodeNet.ListenOptions;
|
|
83
|
+
node?: (NodeHttp.ServerOptions | NodeHttps.ServerOptions) & NodeNet.ListenOptions;
|
|
58
84
|
/**
|
|
59
85
|
* Bun server options
|
|
60
86
|
*
|
|
61
87
|
* @docs https://bun.sh/docs/api/http
|
|
62
88
|
*/
|
|
63
|
-
bun?: Omit<Bun.ServeOptions, "fetch">;
|
|
89
|
+
bun?: Omit<Bun.ServeOptions | Bun.TLSServeOptions, "fetch">;
|
|
64
90
|
/**
|
|
65
91
|
* Deno server options
|
|
66
92
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srvx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Universal Server API based on web platform standards. Works seamlessly with Deno, Bun and Node.js.",
|
|
5
5
|
"repository": "unjs/srvx",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dev": "vitest dev",
|
|
31
31
|
"lint": "eslint . && prettier -c .",
|
|
32
32
|
"lint:fix": "automd && eslint . --fix && prettier -w .",
|
|
33
|
+
"play:mkcert": "openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj /CN=srvx.local",
|
|
33
34
|
"play:node": "node playground/app.mjs",
|
|
34
35
|
"play:deno": "deno run -A playground/app.mjs",
|
|
35
36
|
"play:bun": "bun playground/app.mjs",
|
|
@@ -46,16 +47,16 @@
|
|
|
46
47
|
"cookie-es": "^2.0.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"@cloudflare/workers-types": "^4.
|
|
50
|
-
"@hono/node-server": "^1.
|
|
50
|
+
"@cloudflare/workers-types": "^4.20250321.0",
|
|
51
|
+
"@hono/node-server": "^1.14.0",
|
|
51
52
|
"@mjackson/node-fetch-server": "^0.6.1",
|
|
52
|
-
"@types/bun": "^1.2.
|
|
53
|
+
"@types/bun": "^1.2.5",
|
|
53
54
|
"@types/deno": "^2.2.0",
|
|
54
|
-
"@types/node": "^22.13.
|
|
55
|
-
"@vitest/coverage-v8": "^3.0.
|
|
55
|
+
"@types/node": "^22.13.13",
|
|
56
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
56
57
|
"automd": "^0.4.0",
|
|
57
58
|
"changelogen": "^0.6.1",
|
|
58
|
-
"eslint": "^9.
|
|
59
|
+
"eslint": "^9.23.0",
|
|
59
60
|
"eslint-config-unjs": "^0.4.2",
|
|
60
61
|
"execa": "^9.5.2",
|
|
61
62
|
"get-port-please": "^3.1.2",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"prettier": "^3.5.3",
|
|
64
65
|
"typescript": "^5.8.2",
|
|
65
66
|
"unbuild": "^3.5.0",
|
|
66
|
-
"vitest": "^3.0.
|
|
67
|
+
"vitest": "^3.0.9"
|
|
67
68
|
},
|
|
68
|
-
"packageManager": "pnpm@10.6.
|
|
69
|
+
"packageManager": "pnpm@10.6.5"
|
|
69
70
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
function resolvePort(portOptions, portEnv) {
|
|
2
|
-
const portInput = portOptions ?? portEnv;
|
|
3
|
-
if (portInput === void 0) {
|
|
4
|
-
return 3e3;
|
|
5
|
-
}
|
|
6
|
-
return typeof portInput === "number" ? portInput : Number.parseInt(portInput, 10);
|
|
7
|
-
}
|
|
8
|
-
function fmtURL(host, port, ssl) {
|
|
9
|
-
if (!host || !port) {
|
|
10
|
-
return void 0;
|
|
11
|
-
}
|
|
12
|
-
if (host.includes(":")) {
|
|
13
|
-
host = `[${host}]`;
|
|
14
|
-
}
|
|
15
|
-
return `http${""}://${host}:${port}/`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export { fmtURL as f, resolvePort as r };
|