srvx 0.2.5 → 0.2.7

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,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
 
@@ -1,6 +1,7 @@
1
- import { ServerOptions, Server, BunFetchandler } from '../types.mjs';
1
+ import { ServerOptions, Server, BunFetchHandler } 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
 
@@ -12,12 +13,12 @@ declare const Response: {
12
13
  redirect(url: string | URL, status?: number): Response;
13
14
  };
14
15
  declare function serve(options: ServerOptions): BunServer;
15
- declare class BunServer implements Server<BunFetchandler> {
16
+ declare class BunServer implements Server<BunFetchHandler> {
16
17
  readonly runtime = "bun";
17
18
  readonly options: ServerOptions;
18
19
  readonly bun: Server["bun"];
19
- readonly serveOptions: Bun.ServeOptions;
20
- readonly fetch: BunFetchandler;
20
+ readonly serveOptions: Bun.ServeOptions | Bun.TLSServeOptions;
21
+ readonly fetch: BunFetchHandler;
21
22
  constructor(options: ServerOptions);
22
23
  serve(): Promise<Awaited<this>>;
23
24
  get url(): string | undefined;
@@ -1,5 +1,6 @@
1
- import { r as resolvePort } from '../shared/srvx.PbkQy9Ck.mjs';
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) {
@@ -1,6 +1,7 @@
1
1
  import { ServerOptions, Server } from '../types.mjs';
2
2
  import * as CF from '@cloudflare/workers-types';
3
3
  import 'node:http';
4
+ import 'node:https';
4
5
  import 'node:net';
5
6
  import 'bun';
6
7
 
@@ -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>;
@@ -1,5 +1,6 @@
1
- import { r as resolvePort, f as fmtURL } from '../shared/srvx.PbkQy9Ck.mjs';
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(this.#listeningInfo.hostname, this.#listeningInfo.port) : void 0;
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);
@@ -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
- "__#4201@#body"?: BodyInit | null;
17
- "__#4201@#init"?: ResponseInit;
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
- "__#4201@#responseObj"?: Response;
29
+ "__#4363@#responseObj"?: globalThis.Response;
29
30
  /** Lazy initialized headers instance */
30
- "__#4201@#headersObj"?: Headers;
31
- clone(): Response;
32
- readonly "__#4201@#response": Response;
31
+ "__#4363@#headersObj"?: Headers;
32
+ clone(): globalThis.Response;
33
+ readonly "__#4363@#response": globalThis.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
- "__#4201@#fastBody"<T extends object>(as: new (...args: any[]) => T): T | null | false;
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>;
@@ -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.PbkQy9Ck.mjs';
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(webRes.status || 200, webRes.statusText, headerEntries);
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
  }
@@ -83,19 +89,18 @@ function streamBody(stream, nodeRes) {
83
89
  });
84
90
  }
85
91
 
86
- const kNodeReq = /* @__PURE__ */ Symbol.for("srvx.node.request");
87
92
  const kNodeInspect = /* @__PURE__ */ Symbol.for(
88
93
  "nodejs.util.inspect.custom"
89
94
  );
90
95
 
91
96
  const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
92
- class NodeReqHeadersProxy2 {
93
- constructor(req) {
94
- this[kNodeReq] = req;
97
+ const _Headers = class Headers {
98
+ constructor(nodeCtx) {
99
+ this.node = nodeCtx;
95
100
  }
96
101
  append(name, value) {
97
102
  name = name.toLowerCase();
98
- const _headers = this[kNodeReq].headers;
103
+ const _headers = this.node.req.headers;
99
104
  const _current = _headers[name];
100
105
  if (_current) {
101
106
  if (Array.isArray(_current)) {
@@ -109,14 +114,14 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
109
114
  }
110
115
  delete(name) {
111
116
  name = name.toLowerCase();
112
- this[kNodeReq].headers[name] = void 0;
117
+ this.node.req.headers[name] = void 0;
113
118
  }
114
119
  get(name) {
115
120
  name = name.toLowerCase();
116
- return _normalizeValue(this[kNodeReq].headers[name]);
121
+ return _normalizeValue(this.node.req.headers[name]);
117
122
  }
118
123
  getSetCookie() {
119
- const setCookie = this[kNodeReq].headers["set-cookie"];
124
+ const setCookie = this.node.req.headers["set-cookie"];
120
125
  if (!setCookie || setCookie.length === 0) {
121
126
  return [];
122
127
  }
@@ -124,11 +129,11 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
124
129
  }
125
130
  has(name) {
126
131
  name = name.toLowerCase();
127
- return !!this[kNodeReq].headers[name];
132
+ return !!this.node.req.headers[name];
128
133
  }
129
134
  set(name, value) {
130
135
  name = name.toLowerCase();
131
- this[kNodeReq].headers[name] = value;
136
+ this.node.req.headers[name] = value;
132
137
  }
133
138
  get count() {
134
139
  throw new Error("Method not implemented.");
@@ -137,7 +142,7 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
137
142
  throw new Error("Method not implemented.");
138
143
  }
139
144
  toJSON() {
140
- const _headers = this[kNodeReq].headers;
145
+ const _headers = this.node.req.headers;
141
146
  const result = {};
142
147
  for (const key in _headers) {
143
148
  if (_headers[key]) {
@@ -147,7 +152,7 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
147
152
  return result;
148
153
  }
149
154
  forEach(cb, thisArg) {
150
- const _headers = this[kNodeReq].headers;
155
+ const _headers = this.node.req.headers;
151
156
  for (const key in _headers) {
152
157
  if (_headers[key]) {
153
158
  cb.call(
@@ -160,24 +165,24 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
160
165
  }
161
166
  }
162
167
  *entries() {
163
- const _headers = this[kNodeReq].headers;
168
+ const _headers = this.node.req.headers;
164
169
  for (const key in _headers) {
165
170
  yield [key, _normalizeValue(_headers[key])];
166
171
  }
167
172
  }
168
173
  *keys() {
169
- const keys = Object.keys(this[kNodeReq].headers);
174
+ const keys = Object.keys(this.node.req.headers);
170
175
  for (const key of keys) {
171
176
  yield key;
172
177
  }
173
178
  }
174
179
  *values() {
175
- const values = Object.values(this[kNodeReq].headers);
180
+ const values = Object.values(this.node.req.headers);
176
181
  for (const value of values) {
177
182
  yield _normalizeValue(value);
178
183
  }
179
184
  }
180
- [(Symbol.iterator)]() {
185
+ [Symbol.iterator]() {
181
186
  return this.entries()[Symbol.iterator]();
182
187
  }
183
188
  get [Symbol.toStringTag]() {
@@ -186,9 +191,9 @@ const NodeReqHeadersProxy = /* @__PURE__ */ (() => {
186
191
  [kNodeInspect]() {
187
192
  return Object.fromEntries(this.entries());
188
193
  }
189
- }
190
- Object.setPrototypeOf(NodeReqHeadersProxy2.prototype, Headers.prototype);
191
- return NodeReqHeadersProxy2;
194
+ };
195
+ Object.setPrototypeOf(_Headers.prototype, globalThis.Headers.prototype);
196
+ return _Headers;
192
197
  })();
193
198
  function _normalizeValue(value) {
194
199
  if (Array.isArray(value)) {
@@ -197,149 +202,171 @@ function _normalizeValue(value) {
197
202
  return value || "";
198
203
  }
199
204
 
200
- const NodeReqURLProxy = /* @__PURE__ */ (() => class _NodeReqURLProxy {
201
- constructor(req) {
202
- this.hash = "";
203
- this.password = "";
204
- this.username = "";
205
- this[kNodeReq] = req;
206
- }
207
- // host
208
- get host() {
209
- return this[kNodeReq].headers.host || "";
210
- }
211
- set host(value) {
212
- this._hostname = void 0;
213
- this._port = void 0;
214
- this[kNodeReq].headers.host = value;
215
- }
216
- // hostname
217
- get hostname() {
218
- if (this._hostname === void 0) {
219
- const [hostname, port] = parseHost(this[kNodeReq].headers.host);
220
- if (this._port === void 0 && port) {
221
- this._port = String(Number.parseInt(port) || "");
205
+ const NodeReqURLProxy = /* @__PURE__ */ (() => {
206
+ const _URL = class URL {
207
+ constructor(nodeCtx) {
208
+ this._hash = "";
209
+ this._username = "";
210
+ this._password = "";
211
+ this.node = nodeCtx;
212
+ }
213
+ get hash() {
214
+ return this._hash;
215
+ }
216
+ set hash(value) {
217
+ this._hash = value;
218
+ }
219
+ get username() {
220
+ return this._username;
221
+ }
222
+ set username(value) {
223
+ this._username = value;
224
+ }
225
+ get password() {
226
+ return this._password;
227
+ }
228
+ set password(value) {
229
+ this._password = value;
230
+ }
231
+ // host
232
+ get host() {
233
+ return this.node.req.headers.host || "";
234
+ }
235
+ set host(value) {
236
+ this._hostname = void 0;
237
+ this._port = void 0;
238
+ this.node.req.headers.host = value;
239
+ }
240
+ // hostname
241
+ get hostname() {
242
+ if (this._hostname === void 0) {
243
+ const [hostname, port] = parseHost(this.node.req.headers.host);
244
+ if (this._port === void 0 && port) {
245
+ this._port = String(Number.parseInt(port) || "");
246
+ }
247
+ this._hostname = hostname || "localhost";
222
248
  }
223
- this._hostname = hostname || "localhost";
249
+ return this._hostname;
224
250
  }
225
- return this._hostname;
226
- }
227
- set hostname(value) {
228
- this._hostname = value;
229
- }
230
- // port
231
- get port() {
232
- if (this._port === void 0) {
233
- const [hostname, port] = parseHost(this[kNodeReq].headers.host);
234
- if (this._hostname === void 0 && hostname) {
235
- this._hostname = hostname;
251
+ set hostname(value) {
252
+ this._hostname = value;
253
+ }
254
+ // port
255
+ get port() {
256
+ if (this._port === void 0) {
257
+ const [hostname, port] = parseHost(this.node.req.headers.host);
258
+ if (this._hostname === void 0 && hostname) {
259
+ this._hostname = hostname;
260
+ }
261
+ this._port = port || String(this.node.req.socket?.localPort || "");
236
262
  }
237
- this._port = port || String(this[kNodeReq].socket?.localPort || "");
263
+ return this._port;
238
264
  }
239
- return this._port;
240
- }
241
- set port(value) {
242
- this._port = String(Number.parseInt(value) || "");
243
- }
244
- // pathname
245
- get pathname() {
246
- if (this._pathname === void 0) {
247
- const [pathname, search] = parsePath(this[kNodeReq].url || "/");
248
- this._pathname = pathname;
265
+ set port(value) {
266
+ this._port = String(Number.parseInt(value) || "");
267
+ }
268
+ // pathname
269
+ get pathname() {
270
+ if (this._pathname === void 0) {
271
+ const [pathname, search] = parsePath(this.node.req.url || "/");
272
+ this._pathname = pathname;
273
+ if (this._search === void 0) {
274
+ this._search = search;
275
+ }
276
+ }
277
+ return this._pathname;
278
+ }
279
+ set pathname(value) {
280
+ if (value[0] !== "/") {
281
+ value = "/" + value;
282
+ }
283
+ if (value === this._pathname) {
284
+ return;
285
+ }
286
+ this._pathname = value;
287
+ this.node.req.url = value + this.search;
288
+ }
289
+ // search
290
+ get search() {
249
291
  if (this._search === void 0) {
292
+ const [pathname, search] = parsePath(this.node.req.url || "/");
250
293
  this._search = search;
294
+ if (this._pathname === void 0) {
295
+ this._pathname = pathname;
296
+ }
251
297
  }
298
+ return this._search;
252
299
  }
253
- return this._pathname;
254
- }
255
- set pathname(value) {
256
- if (value[0] !== "/") {
257
- value = "/" + value;
300
+ set search(value) {
301
+ if (value === "?") {
302
+ value = "";
303
+ } else if (value && value[0] !== "?") {
304
+ value = "?" + value;
305
+ }
306
+ if (value === this._search) {
307
+ return;
308
+ }
309
+ this._search = value;
310
+ this._searchParams = void 0;
311
+ this.node.req.url = this.pathname + value;
258
312
  }
259
- if (value === this._pathname) {
260
- return;
313
+ // searchParams
314
+ get searchParams() {
315
+ if (!this._searchParams) {
316
+ this._searchParams = new URLSearchParams(this.search);
317
+ }
318
+ return this._searchParams;
261
319
  }
262
- this._pathname = value;
263
- this[kNodeReq].url = value + this.search;
264
- }
265
- // search
266
- get search() {
267
- if (this._search === void 0) {
268
- const [pathname, search] = parsePath(this[kNodeReq].url || "/");
269
- this._search = search;
270
- if (this._pathname === void 0) {
271
- this._pathname = pathname;
320
+ set searchParams(value) {
321
+ this._searchParams = value;
322
+ this._search = value.toString();
323
+ }
324
+ // protocol
325
+ get protocol() {
326
+ if (!this._protocol) {
327
+ this._protocol = this.node.req.socket?.encrypted || this.node.req.headers["x-forwarded-proto"] === "https" ? "https:" : "http:";
272
328
  }
329
+ return this._protocol;
273
330
  }
274
- return this._search;
275
- }
276
- set search(value) {
277
- if (value === "?") {
278
- value = "";
279
- } else if (value && value[0] !== "?") {
280
- value = "?" + value;
331
+ set protocol(value) {
332
+ this._protocol = value;
281
333
  }
282
- if (value === this._search) {
283
- return;
334
+ // origin
335
+ get origin() {
336
+ return `${this.protocol}//${this.host}`;
284
337
  }
285
- this._search = value;
286
- this._searchParams = void 0;
287
- this[kNodeReq].url = this.pathname + value;
288
- }
289
- // searchParams
290
- get searchParams() {
291
- if (!this._searchParams) {
292
- this._searchParams = new URLSearchParams(this.search);
338
+ set origin(_value) {
293
339
  }
294
- return this._searchParams;
295
- }
296
- set searchParams(value) {
297
- this._searchParams = value;
298
- this._search = value.toString();
299
- }
300
- // protocol
301
- get protocol() {
302
- if (!this._protocol) {
303
- this._protocol = this[kNodeReq].socket?.encrypted || this[kNodeReq].headers["x-forwarded-proto"] === "https" ? "https:" : "http:";
340
+ // href
341
+ get href() {
342
+ return `${this.protocol}//${this.host}${this.pathname}${this.search}`;
304
343
  }
305
- return this._protocol;
306
- }
307
- set protocol(value) {
308
- this._protocol = value;
309
- }
310
- // origin
311
- get origin() {
312
- return `${this.protocol}//${this.host}`;
313
- }
314
- set origin(_value) {
315
- }
316
- // href
317
- get href() {
318
- return `${this.protocol}//${this.host}${this.pathname}${this.search}`;
319
- }
320
- set href(value) {
321
- const _url = new URL(value);
322
- this._protocol = _url.protocol;
323
- this.username = _url.username;
324
- this.password = _url.password;
325
- this._hostname = _url.hostname;
326
- this._port = _url.port;
327
- this.pathname = _url.pathname;
328
- this.search = _url.search;
329
- this.hash = _url.hash;
330
- }
331
- toString() {
332
- return this.href;
333
- }
334
- toJSON() {
335
- return this.href;
336
- }
337
- get [(Symbol.toStringTag)]() {
338
- return "URL";
339
- }
340
- [kNodeInspect]() {
341
- return this.href;
342
- }
344
+ set href(value) {
345
+ const _url = new globalThis.URL(value);
346
+ this._protocol = _url.protocol;
347
+ this.username = _url.username;
348
+ this.password = _url.password;
349
+ this._hostname = _url.hostname;
350
+ this._port = _url.port;
351
+ this.pathname = _url.pathname;
352
+ this.search = _url.search;
353
+ this.hash = _url.hash;
354
+ }
355
+ toString() {
356
+ return this.href;
357
+ }
358
+ toJSON() {
359
+ return this.href;
360
+ }
361
+ get [Symbol.toStringTag]() {
362
+ return "URL";
363
+ }
364
+ [kNodeInspect]() {
365
+ return this.href;
366
+ }
367
+ };
368
+ Object.setPrototypeOf(_URL.prototype, globalThis.URL.prototype);
369
+ return _URL;
343
370
  })();
344
371
  function parsePath(input) {
345
372
  const url = (input || "/").replace(/\\/g, "/");
@@ -355,7 +382,7 @@ function parseHost(host) {
355
382
  }
356
383
 
357
384
  const NodeRequestProxy = /* @__PURE__ */ (() => {
358
- class NodeRequestProxy2 {
385
+ const _Request = class Request {
359
386
  #url;
360
387
  #headers;
361
388
  #bodyUsed = false;
@@ -367,29 +394,32 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
367
394
  #jsonBody;
368
395
  #textBody;
369
396
  #bodyStream;
370
- constructor(nodeReq) {
371
- this[kNodeReq] = nodeReq;
397
+ constructor(nodeCtx) {
398
+ this.node = nodeCtx;
372
399
  }
373
400
  get headers() {
374
401
  if (!this.#headers) {
375
- this.#headers = new NodeReqHeadersProxy(this[kNodeReq]);
402
+ this.#headers = new NodeReqHeadersProxy(this.node);
376
403
  }
377
404
  return this.#headers;
378
405
  }
379
406
  get remoteAddress() {
380
- return this[kNodeReq].socket?.remoteAddress;
407
+ return this.node.req.socket?.remoteAddress;
381
408
  }
382
409
  clone() {
383
- return new NodeRequestProxy2(this[kNodeReq]);
410
+ return new _Request({ ...this.node });
384
411
  }
385
- get url() {
412
+ get _url() {
386
413
  if (!this.#url) {
387
- this.#url = new NodeReqURLProxy(this[kNodeReq]);
414
+ this.#url = new NodeReqURLProxy(this.node);
388
415
  }
389
- return this.#url.href;
416
+ return this.#url;
417
+ }
418
+ get url() {
419
+ return this._url.href;
390
420
  }
391
421
  get method() {
392
- return this[kNodeReq].method || "GET";
422
+ return this.node.req.method || "GET";
393
423
  }
394
424
  get signal() {
395
425
  if (!this.#abortSignal) {
@@ -404,18 +434,19 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
404
434
  if (this.#hasBody !== void 0) {
405
435
  return this.#hasBody;
406
436
  }
407
- const method = this[kNodeReq].method?.toUpperCase();
437
+ const method = this.node.req.method?.toUpperCase();
408
438
  if (!method || !(method === "PATCH" || method === "POST" || method === "PUT" || method === "DELETE")) {
409
439
  this.#hasBody = false;
410
440
  return false;
411
441
  }
412
- if (!Number.parseInt(this[kNodeReq].headers["content-length"] || "")) {
413
- const isChunked = (this[kNodeReq].headers["transfer-encoding"] || "").split(",").map((e) => e.trim()).filter(Boolean).includes("chunked");
442
+ if (!Number.parseInt(this.node.req.headers["content-length"] || "")) {
443
+ const isChunked = (this.node.req.headers["transfer-encoding"] || "").split(",").map((e) => e.trim()).filter(Boolean).includes("chunked");
414
444
  if (!isChunked) {
415
445
  this.#hasBody = false;
416
446
  return false;
417
447
  }
418
448
  }
449
+ this.#hasBody = true;
419
450
  return true;
420
451
  }
421
452
  get body() {
@@ -426,7 +457,7 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
426
457
  this.#bodyUsed = true;
427
458
  this.#bodyStream = new ReadableStream({
428
459
  start: (controller) => {
429
- this[kNodeReq].on("data", (chunk) => {
460
+ this.node.req.on("data", (chunk) => {
430
461
  controller.enqueue(chunk);
431
462
  }).once("error", (error) => {
432
463
  controller.error(error);
@@ -460,7 +491,7 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
460
491
  if (!this.#blobBody) {
461
492
  this.#blobBody = this.bytes().then((bytes) => {
462
493
  return new Blob([bytes], {
463
- type: this[kNodeReq].headers["content-type"]
494
+ type: this.node.req.headers["content-type"]
464
495
  });
465
496
  });
466
497
  }
@@ -490,7 +521,7 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
490
521
  }
491
522
  return this.#jsonBody;
492
523
  }
493
- get [(Symbol.toStringTag)]() {
524
+ get [Symbol.toStringTag]() {
494
525
  return "Request";
495
526
  }
496
527
  [kNodeInspect]() {
@@ -500,9 +531,9 @@ const NodeRequestProxy = /* @__PURE__ */ (() => {
500
531
  headers: this.headers
501
532
  };
502
533
  }
503
- }
504
- Object.setPrototypeOf(NodeRequestProxy2.prototype, Request.prototype);
505
- return NodeRequestProxy2;
534
+ };
535
+ Object.setPrototypeOf(_Request.prototype, globalThis.Request.prototype);
536
+ return _Request;
506
537
  })();
507
538
  async function _readStream(stream) {
508
539
  const chunks = [];
@@ -517,7 +548,7 @@ async function _readStream(stream) {
517
548
  }
518
549
 
519
550
  const NodeFastResponse = /* @__PURE__ */ (() => {
520
- class NodeFastResponse2 {
551
+ const _Response = class Response {
521
552
  #body;
522
553
  #init;
523
554
  constructor(body, init) {
@@ -565,7 +596,7 @@ const NodeFastResponse = /* @__PURE__ */ (() => {
565
596
  } else if (typeof bodyInit.pipe === "function") {
566
597
  body = bodyInit;
567
598
  } else {
568
- const res = new Response(bodyInit);
599
+ const res = new globalThis.Response(bodyInit);
569
600
  body = res.body;
570
601
  for (const [key, value] of res.headers) {
571
602
  headers.push([key, value]);
@@ -590,11 +621,11 @@ const NodeFastResponse = /* @__PURE__ */ (() => {
590
621
  if (this.#responseObj) {
591
622
  return this.#responseObj.clone();
592
623
  }
593
- return new Response(this.#body, this.#init);
624
+ return new globalThis.Response(this.#body, this.#init);
594
625
  }
595
626
  get #response() {
596
627
  if (!this.#responseObj) {
597
- this.#responseObj = new Response(this.#body, this.#init);
628
+ this.#responseObj = new globalThis.Response(this.#body, this.#init);
598
629
  this.#body = void 0;
599
630
  this.#init = void 0;
600
631
  this.#headersObj = void 0;
@@ -733,9 +764,9 @@ const NodeFastResponse = /* @__PURE__ */ (() => {
733
764
  }
734
765
  return this.text().then((text) => JSON.parse(text));
735
766
  }
736
- }
737
- Object.setPrototypeOf(NodeFastResponse2.prototype, Response.prototype);
738
- return NodeFastResponse2;
767
+ };
768
+ Object.setPrototypeOf(_Response.prototype, globalThis.Response.prototype);
769
+ return _Response;
739
770
  })();
740
771
 
741
772
  function serve(options) {
@@ -743,8 +774,7 @@ function serve(options) {
743
774
  }
744
775
  function toNodeHandler(fetchHandler) {
745
776
  return (nodeReq, nodeRes) => {
746
- const request = new NodeRequestProxy(nodeReq);
747
- request.node = { req: nodeReq, res: nodeRes };
777
+ const request = new NodeRequestProxy({ req: nodeReq, res: nodeRes });
748
778
  const res = fetchHandler(request);
749
779
  return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
750
780
  };
@@ -756,18 +786,22 @@ class NodeServer {
756
786
  const fetchHandler = wrapFetch(this, this.options.fetch);
757
787
  this.fetch = fetchHandler;
758
788
  const handler = (nodeReq, nodeRes) => {
759
- const request = new NodeRequestProxy(nodeReq);
760
- request.node = { req: nodeReq, res: nodeRes };
789
+ const request = new NodeRequestProxy({ req: nodeReq, res: nodeRes });
761
790
  const res = fetchHandler(request);
762
791
  return res instanceof Promise ? res.then((resolvedRes) => sendNodeResponse(nodeRes, resolvedRes)) : sendNodeResponse(nodeRes, res);
763
792
  };
793
+ const tls = resolveTLSOptions(this.options);
764
794
  this.serveOptions = {
765
795
  port: resolvePort(this.options.port, globalThis.process?.env.PORT),
766
796
  host: this.options.hostname,
767
797
  exclusive: !this.options.reusePort,
798
+ ...tls ? { cert: tls.cert, key: tls.key, passphrase: tls.passphrase } : {},
768
799
  ...this.options.node
769
800
  };
770
- const server = NodeHttp.createServer(this.serveOptions, handler);
801
+ const server = this.serveOptions.cert ? NodeHttps.createServer(
802
+ this.serveOptions,
803
+ handler
804
+ ) : NodeHttp.createServer(this.serveOptions, handler);
771
805
  this.node = { server, handler };
772
806
  if (!options.manual) {
773
807
  this.serve();
@@ -787,7 +821,11 @@ class NodeServer {
787
821
  if (!addr) {
788
822
  return;
789
823
  }
790
- return typeof addr === "string" ? addr : fmtURL(addr.address, addr.port);
824
+ return typeof addr === "string" ? addr : fmtURL(
825
+ addr.address,
826
+ addr.port,
827
+ this.node.server instanceof NodeHttps.Server
828
+ );
791
829
  }
792
830
  ready() {
793
831
  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
  *
@@ -168,9 +194,9 @@ interface ServerRequest extends Request {
168
194
  };
169
195
  }
170
196
  type FetchHandler = (request: Request) => Response | Promise<Response>;
171
- type BunFetchandler = (request: Request, server?: Bun.Server) => Response | Promise<Response>;
197
+ type BunFetchHandler = (request: Request, server?: Bun.Server) => Response | Promise<Response>;
172
198
  type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
173
199
  type NodeHttpHandler = (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
174
200
  type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
175
201
 
176
- export { type BunFetchandler, type CloudflareFetchHandler, type DenoFetchHandler, type FetchHandler, type NodeHttpHandler, Response, type Server, type ServerHandler, type ServerOptions, type ServerPlugin, type ServerPluginInstance, type ServerRequest, serve };
202
+ export { type BunFetchHandler, type CloudflareFetchHandler, type DenoFetchHandler, type FetchHandler, type NodeHttpHandler, Response, type Server, type ServerHandler, type ServerOptions, type ServerPlugin, type ServerPluginInstance, type ServerRequest, serve };
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "srvx",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
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",
7
7
  "sideEffects": false,
8
8
  "type": "module",
9
- "types": "./dist/types.d.mts",
10
9
  "exports": {
11
10
  "./types": "./dist/types.d.mts",
12
11
  "./deno": "./dist/adapters/deno.mjs",
@@ -21,6 +20,7 @@
21
20
  "types": "./dist/types.d.mts"
22
21
  }
23
22
  },
23
+ "types": "./dist/types.d.mts",
24
24
  "files": [
25
25
  "dist"
26
26
  ],
@@ -30,11 +30,12 @@
30
30
  "dev": "vitest dev",
31
31
  "lint": "eslint . && prettier -c .",
32
32
  "lint:fix": "automd && eslint . --fix && prettier -w .",
33
- "play:node": "node playground/app.mjs",
34
- "play:deno": "deno run -A playground/app.mjs",
33
+ "prepack": "pnpm build",
35
34
  "play:bun": "bun playground/app.mjs",
36
35
  "play:cf": "pnpx wrangler dev playground/app.mjs",
37
- "prepack": "pnpm build",
36
+ "play:deno": "deno run -A playground/app.mjs",
37
+ "play:mkcert": "openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj /CN=srvx.local",
38
+ "play:node": "node playground/app.mjs",
38
39
  "release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
39
40
  "test": "pnpm lint && pnpm test:types && vitest run --coverage",
40
41
  "test:types": "tsc --noEmit --skipLibCheck"
@@ -46,16 +47,16 @@
46
47
  "cookie-es": "^2.0.0"
47
48
  },
48
49
  "devDependencies": {
49
- "@cloudflare/workers-types": "^4.20250303.0",
50
- "@hono/node-server": "^1.13.8",
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.4",
53
+ "@types/bun": "^1.2.5",
53
54
  "@types/deno": "^2.2.0",
54
- "@types/node": "^22.13.10",
55
- "@vitest/coverage-v8": "^3.0.8",
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.22.0",
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,10 @@
63
64
  "prettier": "^3.5.3",
64
65
  "typescript": "^5.8.2",
65
66
  "unbuild": "^3.5.0",
66
- "vitest": "^3.0.8"
67
+ "vitest": "^3.0.9"
67
68
  },
68
- "packageManager": "pnpm@10.6.2"
69
+ "packageManager": "pnpm@10.6.5",
70
+ "engines": {
71
+ "node": ">=20.11.1"
72
+ }
69
73
  }
@@ -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 };