mikroserve 0.0.5 → 0.0.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
@@ -2,8 +2,13 @@
2
2
 
3
3
  **Minimalistic, ready-to-use API, built on Node.js primitives**.
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/mikroserve.svg)](https://www.npmjs.com/package/mikroserve)
6
+
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/mikroserve)](https://bundlephobia.com/package/mikroserve)
8
+
5
9
  ![Build Status](https://github.com/mikaelvesavuori/mikroserve/workflows/main/badge.svg)
6
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
10
+
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
12
 
8
13
  ---
9
14
 
@@ -13,7 +18,7 @@
13
18
  - Supports custom middlewares
14
19
  - Out-of-the-box CORS support
15
20
  - Built-in customizable rate limiter
16
- - Tiny (~4.9kb gzipped)
21
+ - Tiny (~4.8kb gzipped)
17
22
  - Only a single dependency: [MikroConf](https://github.com/mikaelvesavuori/mikroconf)
18
23
 
19
24
  ## Installation
@@ -1,6 +1,7 @@
1
- import http from 'node:http';
2
- import https from 'node:https';
3
- import { MikroServeOptions, Middleware, RouteHandler } from './interfaces/index.mjs';
1
+ import { MikroServeOptions, Middleware, RouteHandler, ServerType } from './interfaces/index.mjs';
2
+ import 'node:http';
3
+ import 'node:http2';
4
+ import 'node:https';
4
5
 
5
6
  /**
6
7
  * @description MikroServe manages HTTP server operations with routing.
@@ -44,11 +45,11 @@ declare class MikroServe {
44
45
  /**
45
46
  * @description Creates an HTTP/HTTPS server, sets up graceful shutdown, and starts listening.
46
47
  */
47
- start(): http.Server | https.Server;
48
+ start(): ServerType;
48
49
  /**
49
50
  * @description Creates and configures a server instance without starting it.
50
51
  */
51
- createServer(): http.Server | https.Server;
52
+ createServer(): ServerType;
52
53
  /**
53
54
  * @description Rate limiting middleware.
54
55
  */
@@ -80,7 +81,7 @@ declare class MikroServe {
80
81
  /**
81
82
  * @description Sets up graceful shutdown handlers for a server.
82
83
  */
83
- setupGracefulShutdown(server: http.Server | https.Server): void;
84
+ setupGracefulShutdown(server: ServerType): void;
84
85
  }
85
86
 
86
87
  export { MikroServe };
@@ -1,6 +1,7 @@
1
- import http from 'node:http';
2
- import https from 'node:https';
3
- import { MikroServeOptions, Middleware, RouteHandler } from './interfaces/index.js';
1
+ import { MikroServeOptions, Middleware, RouteHandler, ServerType } from './interfaces/index.js';
2
+ import 'node:http';
3
+ import 'node:http2';
4
+ import 'node:https';
4
5
 
5
6
  /**
6
7
  * @description MikroServe manages HTTP server operations with routing.
@@ -44,11 +45,11 @@ declare class MikroServe {
44
45
  /**
45
46
  * @description Creates an HTTP/HTTPS server, sets up graceful shutdown, and starts listening.
46
47
  */
47
- start(): http.Server | https.Server;
48
+ start(): ServerType;
48
49
  /**
49
50
  * @description Creates and configures a server instance without starting it.
50
51
  */
51
- createServer(): http.Server | https.Server;
52
+ createServer(): ServerType;
52
53
  /**
53
54
  * @description Rate limiting middleware.
54
55
  */
@@ -80,7 +81,7 @@ declare class MikroServe {
80
81
  /**
81
82
  * @description Sets up graceful shutdown handlers for a server.
82
83
  */
83
- setupGracefulShutdown(server: http.Server | https.Server): void;
84
+ setupGracefulShutdown(server: ServerType): void;
84
85
  }
85
86
 
86
87
  export { MikroServe };
package/lib/MikroServe.js CHANGED
@@ -35,6 +35,7 @@ __export(MikroServe_exports, {
35
35
  module.exports = __toCommonJS(MikroServe_exports);
36
36
  var import_node_fs = require("fs");
37
37
  var import_node_http = __toESM(require("http"));
38
+ var import_node_http2 = __toESM(require("http2"));
38
39
  var import_node_https = __toESM(require("https"));
39
40
  var import_mikroconf = require("mikroconf");
40
41
 
@@ -281,18 +282,13 @@ var Router = class {
281
282
  }
282
283
  };
283
284
 
284
- // src/utils/getTruthyValue.ts
285
- function getTruthyValue(value) {
286
- if (value === "true" || value === true) return true;
287
- return false;
288
- }
289
-
290
285
  // src/utils/configDefaults.ts
291
286
  var configDefaults = () => {
292
287
  return {
293
288
  port: Number(process.env.PORT) || 3e3,
294
289
  host: process.env.HOST || "0.0.0.0",
295
290
  useHttps: false,
291
+ useHttp2: false,
296
292
  sslCert: "",
297
293
  sslKey: "",
298
294
  sslCa: "",
@@ -304,6 +300,10 @@ var configDefaults = () => {
304
300
  allowedDomains: ["*"]
305
301
  };
306
302
  };
303
+ function getTruthyValue(value) {
304
+ if (value === "true" || value === true) return true;
305
+ return false;
306
+ }
307
307
 
308
308
  // src/MikroServe.ts
309
309
  var MikroServe = class {
@@ -322,6 +322,7 @@ var MikroServe = class {
322
322
  { flag: "--port", path: "port", defaultValue: defaults.port },
323
323
  { flag: "--host", path: "host", defaultValue: defaults.host },
324
324
  { flag: "--https", path: "useHttps", defaultValue: defaults.useHttps, isFlag: true },
325
+ { flag: "--http2", path: "useHttp2", defaultValue: defaults.useHttp2, isFlag: true },
325
326
  { flag: "--cert", path: "sslCert", defaultValue: defaults.sslCert },
326
327
  { flag: "--key", path: "sslKey", defaultValue: defaults.sslKey },
327
328
  { flag: "--ca", path: "sslCa", defaultValue: defaults.sslCa },
@@ -423,7 +424,22 @@ var MikroServe = class {
423
424
  */
424
425
  createServer() {
425
426
  const boundRequestHandler = this.requestHandler.bind(this);
426
- if (this.config.useHttps) {
427
+ if (this.config.useHttp2) {
428
+ if (!this.config.sslCert || !this.config.sslKey)
429
+ throw new Error("SSL certificate and key paths are required when useHttp2 is true");
430
+ try {
431
+ const httpsOptions = {
432
+ key: (0, import_node_fs.readFileSync)(this.config.sslKey),
433
+ cert: (0, import_node_fs.readFileSync)(this.config.sslCert),
434
+ ...this.config.sslCa ? { ca: (0, import_node_fs.readFileSync)(this.config.sslCa) } : {}
435
+ };
436
+ return import_node_http2.default.createSecureServer(httpsOptions, boundRequestHandler);
437
+ } catch (error) {
438
+ if (error.message.includes("key values mismatch"))
439
+ throw new Error(`SSL certificate and key do not match: ${error.message}`);
440
+ throw error;
441
+ }
442
+ } else if (this.config.useHttps) {
427
443
  if (!this.config.sslCert || !this.config.sslKey)
428
444
  throw new Error("SSL certificate and key paths are required when useHttps is true");
429
445
  try {
@@ -477,8 +493,14 @@ var MikroServe = class {
477
493
  this.setSecurityHeaders(res, this.config.useHttps);
478
494
  if (isDebug) console.log(`${method} ${url}`);
479
495
  if (req.method === "OPTIONS") {
480
- res.statusCode = 204;
481
- res.end();
496
+ if (res instanceof import_node_http.default.ServerResponse) {
497
+ res.statusCode = 204;
498
+ res.end();
499
+ } else {
500
+ const h2Res = res;
501
+ h2Res.writeHead(204);
502
+ h2Res.end();
503
+ }
482
504
  return;
483
505
  }
484
506
  try {
@@ -494,7 +516,10 @@ var MikroServe = class {
494
516
  });
495
517
  }
496
518
  const result = await this.router.handle(req, res);
497
- if (result) return this.respond(res, result);
519
+ if (result) {
520
+ if (result._handled) return;
521
+ return this.respond(res, result);
522
+ }
498
523
  return this.respond(res, {
499
524
  statusCode: 404,
500
525
  body: {
@@ -600,14 +625,25 @@ var MikroServe = class {
600
625
  * @description Set security headers.
601
626
  */
602
627
  setSecurityHeaders(res, isHttps = false) {
603
- res.setHeader("X-Content-Type-Options", "nosniff");
604
- res.setHeader("X-Frame-Options", "DENY");
605
- res.setHeader(
606
- "Content-Security-Policy",
607
- "default-src 'self'; script-src 'self'; object-src 'none'"
608
- );
609
- if (isHttps) res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
610
- res.setHeader("X-XSS-Protection", "1; mode=block");
628
+ const securityHeaders = {
629
+ "X-Content-Type-Options": "nosniff",
630
+ "X-Frame-Options": "DENY",
631
+ "Content-Security-Policy": "default-src 'self'; script-src 'self'; object-src 'none'",
632
+ "X-XSS-Protection": "1; mode=block"
633
+ };
634
+ if (isHttps || this.config.useHttp2) {
635
+ securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
636
+ }
637
+ if (res instanceof import_node_http.default.ServerResponse) {
638
+ Object.entries(securityHeaders).forEach(([name, value]) => {
639
+ res.setHeader(name, value);
640
+ });
641
+ } else {
642
+ const h2Res = res;
643
+ Object.entries(securityHeaders).forEach(([name, value]) => {
644
+ h2Res.setHeader(name, value);
645
+ });
646
+ }
611
647
  }
612
648
  /**
613
649
  * @description Sends a response with appropriate headers.
@@ -616,10 +652,21 @@ var MikroServe = class {
616
652
  const headers = {
617
653
  ...response.headers || {}
618
654
  };
619
- res.writeHead(response.statusCode, headers);
620
- if (response.body === null || response.body === void 0) res.end();
621
- else if (typeof response.body === "string") res.end(response.body);
622
- else res.end(JSON.stringify(response.body));
655
+ const hasWriteHead = (res2) => {
656
+ return typeof res2.writeHead === "function" && typeof res2.end === "function";
657
+ };
658
+ if (hasWriteHead(res)) {
659
+ res.writeHead(response.statusCode, headers);
660
+ if (response.body === null || response.body === void 0) res.end();
661
+ else if (typeof response.body === "string") res.end(response.body);
662
+ else res.end(JSON.stringify(response.body));
663
+ } else {
664
+ console.warn("Unexpected response object type without writeHead/end methods");
665
+ res.writeHead?.(response.statusCode, headers);
666
+ if (response.body === null || response.body === void 0) res.end?.();
667
+ else if (typeof response.body === "string") res.end?.(response.body);
668
+ else res.end?.(JSON.stringify(response.body));
669
+ }
623
670
  }
624
671
  /**
625
672
  * @description Sets up graceful shutdown handlers for a server.
@@ -1,10 +1,9 @@
1
1
  import {
2
2
  MikroServe
3
- } from "./chunk-BUKLE4JV.mjs";
3
+ } from "./chunk-J2WPHNMW.mjs";
4
4
  import "./chunk-ZFBBESGU.mjs";
5
5
  import "./chunk-KJT4SET2.mjs";
6
- import "./chunk-NSHBEU32.mjs";
7
- import "./chunk-6HESV5Q6.mjs";
6
+ import "./chunk-JJX5XRNB.mjs";
8
7
  export {
9
8
  MikroServe
10
9
  };
package/lib/Router.d.mts CHANGED
@@ -1,5 +1,7 @@
1
- import http from 'node:http';
2
- import { Middleware, RouteHandler, Route, HandlerResponse } from './interfaces/index.mjs';
1
+ import { Middleware, RouteHandler, Route, RequestType, ResponseType, HandlerResponse } from './interfaces/index.mjs';
2
+ import 'node:http';
3
+ import 'node:http2';
4
+ import 'node:https';
3
5
 
4
6
  /**
5
7
  * Router class to manage routes and middleware
@@ -54,7 +56,7 @@ declare class Router {
54
56
  /**
55
57
  * Handle a request and find the matching route
56
58
  */
57
- handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<HandlerResponse | null>;
59
+ handle(req: RequestType, res: ResponseType): Promise<HandlerResponse | null>;
58
60
  /**
59
61
  * Execute middleware chain and final handler
60
62
  */
package/lib/Router.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import http from 'node:http';
2
- import { Middleware, RouteHandler, Route, HandlerResponse } from './interfaces/index.js';
1
+ import { Middleware, RouteHandler, Route, RequestType, ResponseType, HandlerResponse } from './interfaces/index.js';
2
+ import 'node:http';
3
+ import 'node:http2';
4
+ import 'node:https';
3
5
 
4
6
  /**
5
7
  * Router class to manage routes and middleware
@@ -54,7 +56,7 @@ declare class Router {
54
56
  /**
55
57
  * Handle a request and find the matching route
56
58
  */
57
- handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<HandlerResponse | null>;
59
+ handle(req: RequestType, res: ResponseType): Promise<HandlerResponse | null>;
58
60
  /**
59
61
  * Execute middleware chain and final handler
60
62
  */
@@ -6,11 +6,12 @@ import {
6
6
  } from "./chunk-KJT4SET2.mjs";
7
7
  import {
8
8
  configDefaults
9
- } from "./chunk-NSHBEU32.mjs";
9
+ } from "./chunk-JJX5XRNB.mjs";
10
10
 
11
11
  // src/MikroServe.ts
12
12
  import { readFileSync } from "node:fs";
13
13
  import http from "node:http";
14
+ import http2 from "node:http2";
14
15
  import https from "node:https";
15
16
  import { MikroConf, parsers } from "mikroconf";
16
17
  var MikroServe = class {
@@ -29,6 +30,7 @@ var MikroServe = class {
29
30
  { flag: "--port", path: "port", defaultValue: defaults.port },
30
31
  { flag: "--host", path: "host", defaultValue: defaults.host },
31
32
  { flag: "--https", path: "useHttps", defaultValue: defaults.useHttps, isFlag: true },
33
+ { flag: "--http2", path: "useHttp2", defaultValue: defaults.useHttp2, isFlag: true },
32
34
  { flag: "--cert", path: "sslCert", defaultValue: defaults.sslCert },
33
35
  { flag: "--key", path: "sslKey", defaultValue: defaults.sslKey },
34
36
  { flag: "--ca", path: "sslCa", defaultValue: defaults.sslCa },
@@ -130,7 +132,22 @@ var MikroServe = class {
130
132
  */
131
133
  createServer() {
132
134
  const boundRequestHandler = this.requestHandler.bind(this);
133
- if (this.config.useHttps) {
135
+ if (this.config.useHttp2) {
136
+ if (!this.config.sslCert || !this.config.sslKey)
137
+ throw new Error("SSL certificate and key paths are required when useHttp2 is true");
138
+ try {
139
+ const httpsOptions = {
140
+ key: readFileSync(this.config.sslKey),
141
+ cert: readFileSync(this.config.sslCert),
142
+ ...this.config.sslCa ? { ca: readFileSync(this.config.sslCa) } : {}
143
+ };
144
+ return http2.createSecureServer(httpsOptions, boundRequestHandler);
145
+ } catch (error) {
146
+ if (error.message.includes("key values mismatch"))
147
+ throw new Error(`SSL certificate and key do not match: ${error.message}`);
148
+ throw error;
149
+ }
150
+ } else if (this.config.useHttps) {
134
151
  if (!this.config.sslCert || !this.config.sslKey)
135
152
  throw new Error("SSL certificate and key paths are required when useHttps is true");
136
153
  try {
@@ -184,8 +201,14 @@ var MikroServe = class {
184
201
  this.setSecurityHeaders(res, this.config.useHttps);
185
202
  if (isDebug) console.log(`${method} ${url}`);
186
203
  if (req.method === "OPTIONS") {
187
- res.statusCode = 204;
188
- res.end();
204
+ if (res instanceof http.ServerResponse) {
205
+ res.statusCode = 204;
206
+ res.end();
207
+ } else {
208
+ const h2Res = res;
209
+ h2Res.writeHead(204);
210
+ h2Res.end();
211
+ }
189
212
  return;
190
213
  }
191
214
  try {
@@ -201,7 +224,10 @@ var MikroServe = class {
201
224
  });
202
225
  }
203
226
  const result = await this.router.handle(req, res);
204
- if (result) return this.respond(res, result);
227
+ if (result) {
228
+ if (result._handled) return;
229
+ return this.respond(res, result);
230
+ }
205
231
  return this.respond(res, {
206
232
  statusCode: 404,
207
233
  body: {
@@ -307,14 +333,25 @@ var MikroServe = class {
307
333
  * @description Set security headers.
308
334
  */
309
335
  setSecurityHeaders(res, isHttps = false) {
310
- res.setHeader("X-Content-Type-Options", "nosniff");
311
- res.setHeader("X-Frame-Options", "DENY");
312
- res.setHeader(
313
- "Content-Security-Policy",
314
- "default-src 'self'; script-src 'self'; object-src 'none'"
315
- );
316
- if (isHttps) res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
317
- res.setHeader("X-XSS-Protection", "1; mode=block");
336
+ const securityHeaders = {
337
+ "X-Content-Type-Options": "nosniff",
338
+ "X-Frame-Options": "DENY",
339
+ "Content-Security-Policy": "default-src 'self'; script-src 'self'; object-src 'none'",
340
+ "X-XSS-Protection": "1; mode=block"
341
+ };
342
+ if (isHttps || this.config.useHttp2) {
343
+ securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
344
+ }
345
+ if (res instanceof http.ServerResponse) {
346
+ Object.entries(securityHeaders).forEach(([name, value]) => {
347
+ res.setHeader(name, value);
348
+ });
349
+ } else {
350
+ const h2Res = res;
351
+ Object.entries(securityHeaders).forEach(([name, value]) => {
352
+ h2Res.setHeader(name, value);
353
+ });
354
+ }
318
355
  }
319
356
  /**
320
357
  * @description Sends a response with appropriate headers.
@@ -323,10 +360,21 @@ var MikroServe = class {
323
360
  const headers = {
324
361
  ...response.headers || {}
325
362
  };
326
- res.writeHead(response.statusCode, headers);
327
- if (response.body === null || response.body === void 0) res.end();
328
- else if (typeof response.body === "string") res.end(response.body);
329
- else res.end(JSON.stringify(response.body));
363
+ const hasWriteHead = (res2) => {
364
+ return typeof res2.writeHead === "function" && typeof res2.end === "function";
365
+ };
366
+ if (hasWriteHead(res)) {
367
+ res.writeHead(response.statusCode, headers);
368
+ if (response.body === null || response.body === void 0) res.end();
369
+ else if (typeof response.body === "string") res.end(response.body);
370
+ else res.end(JSON.stringify(response.body));
371
+ } else {
372
+ console.warn("Unexpected response object type without writeHead/end methods");
373
+ res.writeHead?.(response.statusCode, headers);
374
+ if (response.body === null || response.body === void 0) res.end?.();
375
+ else if (typeof response.body === "string") res.end?.(response.body);
376
+ else res.end?.(JSON.stringify(response.body));
377
+ }
330
378
  }
331
379
  /**
332
380
  * @description Sets up graceful shutdown handlers for a server.
@@ -1,13 +1,10 @@
1
- import {
2
- getTruthyValue
3
- } from "./chunk-6HESV5Q6.mjs";
4
-
5
1
  // src/utils/configDefaults.ts
6
2
  var configDefaults = () => {
7
3
  return {
8
4
  port: Number(process.env.PORT) || 3e3,
9
5
  host: process.env.HOST || "0.0.0.0",
10
6
  useHttps: false,
7
+ useHttp2: false,
11
8
  sslCert: "",
12
9
  sslKey: "",
13
10
  sslCa: "",
@@ -25,6 +22,7 @@ var getDefaultConfig = () => {
25
22
  port: defaults.port,
26
23
  host: defaults.host,
27
24
  useHttps: defaults.useHttps,
25
+ useHttp2: defaults.useHttp2,
28
26
  sslCert: defaults.sslCert,
29
27
  sslKey: defaults.sslKey,
30
28
  sslCa: defaults.sslCa,
@@ -36,6 +34,10 @@ var getDefaultConfig = () => {
36
34
  allowedDomains: defaults.allowedDomains
37
35
  };
38
36
  };
37
+ function getTruthyValue(value) {
38
+ if (value === "true" || value === true) return true;
39
+ return false;
40
+ }
39
41
 
40
42
  export {
41
43
  configDefaults,
package/lib/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { MikroServe } from './MikroServe.mjs';
2
2
  export { Context } from './interfaces/index.mjs';
3
3
  import 'node:http';
4
+ import 'node:http2';
4
5
  import 'node:https';
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { MikroServe } from './MikroServe.js';
2
2
  export { Context } from './interfaces/index.js';
3
3
  import 'node:http';
4
+ import 'node:http2';
4
5
  import 'node:https';
package/lib/index.js CHANGED
@@ -37,6 +37,7 @@ module.exports = __toCommonJS(index_exports);
37
37
  // src/MikroServe.ts
38
38
  var import_node_fs = require("fs");
39
39
  var import_node_http = __toESM(require("http"));
40
+ var import_node_http2 = __toESM(require("http2"));
40
41
  var import_node_https = __toESM(require("https"));
41
42
  var import_mikroconf = require("mikroconf");
42
43
 
@@ -283,18 +284,13 @@ var Router = class {
283
284
  }
284
285
  };
285
286
 
286
- // src/utils/getTruthyValue.ts
287
- function getTruthyValue(value) {
288
- if (value === "true" || value === true) return true;
289
- return false;
290
- }
291
-
292
287
  // src/utils/configDefaults.ts
293
288
  var configDefaults = () => {
294
289
  return {
295
290
  port: Number(process.env.PORT) || 3e3,
296
291
  host: process.env.HOST || "0.0.0.0",
297
292
  useHttps: false,
293
+ useHttp2: false,
298
294
  sslCert: "",
299
295
  sslKey: "",
300
296
  sslCa: "",
@@ -306,6 +302,10 @@ var configDefaults = () => {
306
302
  allowedDomains: ["*"]
307
303
  };
308
304
  };
305
+ function getTruthyValue(value) {
306
+ if (value === "true" || value === true) return true;
307
+ return false;
308
+ }
309
309
 
310
310
  // src/MikroServe.ts
311
311
  var MikroServe = class {
@@ -324,6 +324,7 @@ var MikroServe = class {
324
324
  { flag: "--port", path: "port", defaultValue: defaults.port },
325
325
  { flag: "--host", path: "host", defaultValue: defaults.host },
326
326
  { flag: "--https", path: "useHttps", defaultValue: defaults.useHttps, isFlag: true },
327
+ { flag: "--http2", path: "useHttp2", defaultValue: defaults.useHttp2, isFlag: true },
327
328
  { flag: "--cert", path: "sslCert", defaultValue: defaults.sslCert },
328
329
  { flag: "--key", path: "sslKey", defaultValue: defaults.sslKey },
329
330
  { flag: "--ca", path: "sslCa", defaultValue: defaults.sslCa },
@@ -425,7 +426,22 @@ var MikroServe = class {
425
426
  */
426
427
  createServer() {
427
428
  const boundRequestHandler = this.requestHandler.bind(this);
428
- if (this.config.useHttps) {
429
+ if (this.config.useHttp2) {
430
+ if (!this.config.sslCert || !this.config.sslKey)
431
+ throw new Error("SSL certificate and key paths are required when useHttp2 is true");
432
+ try {
433
+ const httpsOptions = {
434
+ key: (0, import_node_fs.readFileSync)(this.config.sslKey),
435
+ cert: (0, import_node_fs.readFileSync)(this.config.sslCert),
436
+ ...this.config.sslCa ? { ca: (0, import_node_fs.readFileSync)(this.config.sslCa) } : {}
437
+ };
438
+ return import_node_http2.default.createSecureServer(httpsOptions, boundRequestHandler);
439
+ } catch (error) {
440
+ if (error.message.includes("key values mismatch"))
441
+ throw new Error(`SSL certificate and key do not match: ${error.message}`);
442
+ throw error;
443
+ }
444
+ } else if (this.config.useHttps) {
429
445
  if (!this.config.sslCert || !this.config.sslKey)
430
446
  throw new Error("SSL certificate and key paths are required when useHttps is true");
431
447
  try {
@@ -479,8 +495,14 @@ var MikroServe = class {
479
495
  this.setSecurityHeaders(res, this.config.useHttps);
480
496
  if (isDebug) console.log(`${method} ${url}`);
481
497
  if (req.method === "OPTIONS") {
482
- res.statusCode = 204;
483
- res.end();
498
+ if (res instanceof import_node_http.default.ServerResponse) {
499
+ res.statusCode = 204;
500
+ res.end();
501
+ } else {
502
+ const h2Res = res;
503
+ h2Res.writeHead(204);
504
+ h2Res.end();
505
+ }
484
506
  return;
485
507
  }
486
508
  try {
@@ -496,7 +518,10 @@ var MikroServe = class {
496
518
  });
497
519
  }
498
520
  const result = await this.router.handle(req, res);
499
- if (result) return this.respond(res, result);
521
+ if (result) {
522
+ if (result._handled) return;
523
+ return this.respond(res, result);
524
+ }
500
525
  return this.respond(res, {
501
526
  statusCode: 404,
502
527
  body: {
@@ -602,14 +627,25 @@ var MikroServe = class {
602
627
  * @description Set security headers.
603
628
  */
604
629
  setSecurityHeaders(res, isHttps = false) {
605
- res.setHeader("X-Content-Type-Options", "nosniff");
606
- res.setHeader("X-Frame-Options", "DENY");
607
- res.setHeader(
608
- "Content-Security-Policy",
609
- "default-src 'self'; script-src 'self'; object-src 'none'"
610
- );
611
- if (isHttps) res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
612
- res.setHeader("X-XSS-Protection", "1; mode=block");
630
+ const securityHeaders = {
631
+ "X-Content-Type-Options": "nosniff",
632
+ "X-Frame-Options": "DENY",
633
+ "Content-Security-Policy": "default-src 'self'; script-src 'self'; object-src 'none'",
634
+ "X-XSS-Protection": "1; mode=block"
635
+ };
636
+ if (isHttps || this.config.useHttp2) {
637
+ securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
638
+ }
639
+ if (res instanceof import_node_http.default.ServerResponse) {
640
+ Object.entries(securityHeaders).forEach(([name, value]) => {
641
+ res.setHeader(name, value);
642
+ });
643
+ } else {
644
+ const h2Res = res;
645
+ Object.entries(securityHeaders).forEach(([name, value]) => {
646
+ h2Res.setHeader(name, value);
647
+ });
648
+ }
613
649
  }
614
650
  /**
615
651
  * @description Sends a response with appropriate headers.
@@ -618,10 +654,21 @@ var MikroServe = class {
618
654
  const headers = {
619
655
  ...response.headers || {}
620
656
  };
621
- res.writeHead(response.statusCode, headers);
622
- if (response.body === null || response.body === void 0) res.end();
623
- else if (typeof response.body === "string") res.end(response.body);
624
- else res.end(JSON.stringify(response.body));
657
+ const hasWriteHead = (res2) => {
658
+ return typeof res2.writeHead === "function" && typeof res2.end === "function";
659
+ };
660
+ if (hasWriteHead(res)) {
661
+ res.writeHead(response.statusCode, headers);
662
+ if (response.body === null || response.body === void 0) res.end();
663
+ else if (typeof response.body === "string") res.end(response.body);
664
+ else res.end(JSON.stringify(response.body));
665
+ } else {
666
+ console.warn("Unexpected response object type without writeHead/end methods");
667
+ res.writeHead?.(response.statusCode, headers);
668
+ if (response.body === null || response.body === void 0) res.end?.();
669
+ else if (typeof response.body === "string") res.end?.(response.body);
670
+ else res.end?.(JSON.stringify(response.body));
671
+ }
625
672
  }
626
673
  /**
627
674
  * @description Sets up graceful shutdown handlers for a server.
package/lib/index.mjs CHANGED
@@ -1,10 +1,9 @@
1
1
  import {
2
2
  MikroServe
3
- } from "./chunk-BUKLE4JV.mjs";
3
+ } from "./chunk-J2WPHNMW.mjs";
4
4
  import "./chunk-ZFBBESGU.mjs";
5
5
  import "./chunk-KJT4SET2.mjs";
6
- import "./chunk-NSHBEU32.mjs";
7
- import "./chunk-6HESV5Q6.mjs";
6
+ import "./chunk-JJX5XRNB.mjs";
8
7
  export {
9
8
  MikroServe
10
9
  };
@@ -1,4 +1,6 @@
1
1
  import http from 'node:http';
2
+ import http2 from 'node:http2';
3
+ import https from 'node:https';
2
4
 
3
5
  /**
4
6
  * @description Server configuration options.
@@ -21,6 +23,11 @@ type MikroServeConfiguration = {
21
23
  * @default false
22
24
  */
23
25
  useHttps: boolean;
26
+ /**
27
+ * Should the server use HTTP2?
28
+ * @default false
29
+ */
30
+ useHttp2: boolean;
24
31
  /**
25
32
  * The path to the SSL certificate.
26
33
  * @default '''
@@ -82,12 +89,12 @@ interface ResponseHelpers {
82
89
  * @description Context object passed to route handlers.
83
90
  */
84
91
  interface Context extends ResponseHelpers {
85
- req: http.IncomingMessage;
86
- res: http.ServerResponse;
92
+ req: http.IncomingMessage | http2.Http2ServerRequest;
93
+ res: http.ServerResponse | http2.Http2ServerResponse;
87
94
  params: Record<string, string>;
88
95
  query: Record<string, string>;
89
96
  body: any;
90
- headers: http.IncomingHttpHeaders;
97
+ headers: http.IncomingHttpHeaders | http2.IncomingHttpHeaders;
91
98
  path: string;
92
99
  state: Record<string, any>;
93
100
  }
@@ -98,6 +105,7 @@ interface HandlerResponse {
98
105
  statusCode: number;
99
106
  body: any;
100
107
  headers?: Record<string, string>;
108
+ _handled?: boolean;
101
109
  }
102
110
  /**
103
111
  * @description Route handler function signature.
@@ -123,5 +131,13 @@ interface PathPattern {
123
131
  pattern: RegExp;
124
132
  paramNames: string[];
125
133
  }
134
+ type ServerType = http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer;
135
+ type RequestType = http.IncomingMessage | http2.Http2ServerRequest;
136
+ type ResponseType = http.ServerResponse | http2.Http2ServerResponse | {
137
+ setHeader: (name: string, value: string) => void;
138
+ getHeaders?: () => Record<string, string | string[] | number>;
139
+ writeHead: (statusCode: number, headers: Record<string, string | string[] | number>) => void;
140
+ statusCode?: number;
141
+ };
126
142
 
127
- export type { Context, HandlerResponse, Middleware, MikroServeConfiguration, MikroServeOptions, PathPattern, ResponseHelpers, Route, RouteHandler };
143
+ export type { Context, HandlerResponse, Middleware, MikroServeConfiguration, MikroServeOptions, PathPattern, RequestType, ResponseHelpers, ResponseType, Route, RouteHandler, ServerType };
@@ -1,4 +1,6 @@
1
1
  import http from 'node:http';
2
+ import http2 from 'node:http2';
3
+ import https from 'node:https';
2
4
 
3
5
  /**
4
6
  * @description Server configuration options.
@@ -21,6 +23,11 @@ type MikroServeConfiguration = {
21
23
  * @default false
22
24
  */
23
25
  useHttps: boolean;
26
+ /**
27
+ * Should the server use HTTP2?
28
+ * @default false
29
+ */
30
+ useHttp2: boolean;
24
31
  /**
25
32
  * The path to the SSL certificate.
26
33
  * @default '''
@@ -82,12 +89,12 @@ interface ResponseHelpers {
82
89
  * @description Context object passed to route handlers.
83
90
  */
84
91
  interface Context extends ResponseHelpers {
85
- req: http.IncomingMessage;
86
- res: http.ServerResponse;
92
+ req: http.IncomingMessage | http2.Http2ServerRequest;
93
+ res: http.ServerResponse | http2.Http2ServerResponse;
87
94
  params: Record<string, string>;
88
95
  query: Record<string, string>;
89
96
  body: any;
90
- headers: http.IncomingHttpHeaders;
97
+ headers: http.IncomingHttpHeaders | http2.IncomingHttpHeaders;
91
98
  path: string;
92
99
  state: Record<string, any>;
93
100
  }
@@ -98,6 +105,7 @@ interface HandlerResponse {
98
105
  statusCode: number;
99
106
  body: any;
100
107
  headers?: Record<string, string>;
108
+ _handled?: boolean;
101
109
  }
102
110
  /**
103
111
  * @description Route handler function signature.
@@ -123,5 +131,13 @@ interface PathPattern {
123
131
  pattern: RegExp;
124
132
  paramNames: string[];
125
133
  }
134
+ type ServerType = http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer;
135
+ type RequestType = http.IncomingMessage | http2.Http2ServerRequest;
136
+ type ResponseType = http.ServerResponse | http2.Http2ServerResponse | {
137
+ setHeader: (name: string, value: string) => void;
138
+ getHeaders?: () => Record<string, string | string[] | number>;
139
+ writeHead: (statusCode: number, headers: Record<string, string | string[] | number>) => void;
140
+ statusCode?: number;
141
+ };
126
142
 
127
- export type { Context, HandlerResponse, Middleware, MikroServeConfiguration, MikroServeOptions, PathPattern, ResponseHelpers, Route, RouteHandler };
143
+ export type { Context, HandlerResponse, Middleware, MikroServeConfiguration, MikroServeOptions, PathPattern, RequestType, ResponseHelpers, ResponseType, Route, RouteHandler, ServerType };
@@ -1,11 +1,14 @@
1
1
  import { MikroServeConfiguration } from '../interfaces/index.mjs';
2
2
  import 'node:http';
3
+ import 'node:http2';
4
+ import 'node:https';
3
5
 
4
6
  declare const configDefaults: () => MikroServeConfiguration;
5
7
  declare const getDefaultConfig: () => {
6
8
  port: number;
7
9
  host: string;
8
10
  useHttps: boolean;
11
+ useHttp2: boolean;
9
12
  sslCert: string;
10
13
  sslKey: string;
11
14
  sslCa: string;
@@ -1,11 +1,14 @@
1
1
  import { MikroServeConfiguration } from '../interfaces/index.js';
2
2
  import 'node:http';
3
+ import 'node:http2';
4
+ import 'node:https';
3
5
 
4
6
  declare const configDefaults: () => MikroServeConfiguration;
5
7
  declare const getDefaultConfig: () => {
6
8
  port: number;
7
9
  host: string;
8
10
  useHttps: boolean;
11
+ useHttp2: boolean;
9
12
  sslCert: string;
10
13
  sslKey: string;
11
14
  sslCa: string;
@@ -24,19 +24,12 @@ __export(configDefaults_exports, {
24
24
  getDefaultConfig: () => getDefaultConfig
25
25
  });
26
26
  module.exports = __toCommonJS(configDefaults_exports);
27
-
28
- // src/utils/getTruthyValue.ts
29
- function getTruthyValue(value) {
30
- if (value === "true" || value === true) return true;
31
- return false;
32
- }
33
-
34
- // src/utils/configDefaults.ts
35
27
  var configDefaults = () => {
36
28
  return {
37
29
  port: Number(process.env.PORT) || 3e3,
38
30
  host: process.env.HOST || "0.0.0.0",
39
31
  useHttps: false,
32
+ useHttp2: false,
40
33
  sslCert: "",
41
34
  sslKey: "",
42
35
  sslCa: "",
@@ -54,6 +47,7 @@ var getDefaultConfig = () => {
54
47
  port: defaults.port,
55
48
  host: defaults.host,
56
49
  useHttps: defaults.useHttps,
50
+ useHttp2: defaults.useHttp2,
57
51
  sslCert: defaults.sslCert,
58
52
  sslKey: defaults.sslKey,
59
53
  sslCa: defaults.sslCa,
@@ -65,6 +59,10 @@ var getDefaultConfig = () => {
65
59
  allowedDomains: defaults.allowedDomains
66
60
  };
67
61
  };
62
+ function getTruthyValue(value) {
63
+ if (value === "true" || value === true) return true;
64
+ return false;
65
+ }
68
66
  // Annotate the CommonJS export names for ESM import in node:
69
67
  0 && (module.exports = {
70
68
  configDefaults,
@@ -1,8 +1,7 @@
1
1
  import {
2
2
  configDefaults,
3
3
  getDefaultConfig
4
- } from "../chunk-NSHBEU32.mjs";
5
- import "../chunk-6HESV5Q6.mjs";
4
+ } from "../chunk-JJX5XRNB.mjs";
6
5
  export {
7
6
  configDefaults,
8
7
  getDefaultConfig
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "mikroserve",
3
3
  "description": "Minimalistic, ready-to-use API, built on Node.js primitives.",
4
- "version": "0.0.5",
4
+ "version": "0.0.7",
5
5
  "author": "Mikael Vesavuori",
6
6
  "license": "MIT",
7
- "keywords": [],
7
+ "keywords": [
8
+ "node",
9
+ "node-server",
10
+ "api",
11
+ "api-server",
12
+ "https"
13
+ ],
8
14
  "main": "lib/index.js",
9
15
  "repository": {
10
16
  "type": "git",
@@ -29,7 +35,7 @@
29
35
  "mikroserve": "lib/index.js"
30
36
  },
31
37
  "scripts": {
32
- "start": "npx tsx src/index.ts --force",
38
+ "start": "npx tsx src/index.ts",
33
39
  "test": "npm run test:licenses && npm run test:types && npm run lint && npm run test:unit",
34
40
  "test:types": "npx type-coverage --at-least 85 --strict --ignore-files \"tests/**/*.ts\" --ignore-files \"*.ts\" --ignore-files \"src/application/errors/*.ts\" --ignore-files \"testdata/*.ts\"",
35
41
  "test:licenses": "npx license-compliance --direct --allow 'MIT;ISC;0BSD;BSD-2-Clause;BSD-3-Clause;Apache-2.0;Unlicense;CC0-1.0'",
@@ -55,6 +61,6 @@
55
61
  "vitest": "2"
56
62
  },
57
63
  "dependencies": {
58
- "mikroconf": "^0.0.2"
64
+ "mikroconf": "latest"
59
65
  }
60
66
  }
@@ -1,9 +0,0 @@
1
- // src/utils/getTruthyValue.ts
2
- function getTruthyValue(value) {
3
- if (value === "true" || value === true) return true;
4
- return false;
5
- }
6
-
7
- export {
8
- getTruthyValue
9
- };
@@ -1,6 +0,0 @@
1
- /**
2
- * @description Check if a value is a boolean or stringly "true".
3
- */
4
- declare function getTruthyValue(value: string | boolean | undefined): boolean;
5
-
6
- export { getTruthyValue };
@@ -1,6 +0,0 @@
1
- /**
2
- * @description Check if a value is a boolean or stringly "true".
3
- */
4
- declare function getTruthyValue(value: string | boolean | undefined): boolean;
5
-
6
- export { getTruthyValue };
@@ -1,33 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/utils/getTruthyValue.ts
21
- var getTruthyValue_exports = {};
22
- __export(getTruthyValue_exports, {
23
- getTruthyValue: () => getTruthyValue
24
- });
25
- module.exports = __toCommonJS(getTruthyValue_exports);
26
- function getTruthyValue(value) {
27
- if (value === "true" || value === true) return true;
28
- return false;
29
- }
30
- // Annotate the CommonJS export names for ESM import in node:
31
- 0 && (module.exports = {
32
- getTruthyValue
33
- });
@@ -1,6 +0,0 @@
1
- import {
2
- getTruthyValue
3
- } from "../chunk-6HESV5Q6.mjs";
4
- export {
5
- getTruthyValue
6
- };