mikroserve 0.0.6 → 0.0.8

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,18 +2,23 @@
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
 
10
15
  - Native Node.js [http](https://nodejs.org/api/http.html)/[https](https://nodejs.org/api/https.html) implementation, meaning maximum performance
11
16
  - [Hono](https://hono.dev)-style API semantics for GET, POST, PATCH, PUT, DELETE operations
12
- - Supports being exposed over both HTTP and HTTPS
17
+ - Supports being exposed over HTTP, HTTPS, and HTTP2
13
18
  - Supports custom middlewares
14
19
  - Out-of-the-box CORS support
15
20
  - Built-in customizable rate limiter
16
- - Tiny (~4.8kb gzipped)
21
+ - Tiny (5kb gzipped)
17
22
  - Only a single dependency: [MikroConf](https://github.com/mikaelvesavuori/mikroconf)
18
23
 
19
24
  ## Installation
@@ -42,7 +47,8 @@ api.get('/', (c: Context) => c.text('Hello world!'));
42
47
  api.start();
43
48
 
44
49
  // The API is ready – go ahead and curl it in your command line of choice
45
- // curl 0.0.0.0:3000
50
+ // HTTP: curl 0.0.0.0:3000
51
+ // HTTPS or HTTP2: curl -k 0.0.0.0:3000
46
52
  // The response should be "Hello world!"
47
53
  ```
48
54
 
@@ -57,6 +63,7 @@ const api = new MikroServe({
57
63
  port: 3000,
58
64
  host: '0.0.0.0',
59
65
  useHttps: false,
66
+ useHttp2: false,
60
67
  sslCert: '',
61
68
  sslKey: '',
62
69
  sslCa: '',
@@ -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,8 +35,9 @@ __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
- var import_mikroconf = require("mikroconf");
40
+ var import_mikroconf2 = require("mikroconf");
40
41
 
41
42
  // src/RateLimiter.ts
42
43
  var RateLimiter = class {
@@ -205,6 +206,16 @@ var Router = class {
205
206
  path,
206
207
  state: {},
207
208
  // Add the missing state property
209
+ raw: () => res,
210
+ binary: (content, contentType = "application/octet-stream", status = 200) => ({
211
+ statusCode: status,
212
+ body: content,
213
+ headers: {
214
+ "Content-Type": contentType,
215
+ "Content-Length": content.length.toString()
216
+ },
217
+ isRaw: true
218
+ }),
208
219
  text: (content, status = 200) => ({
209
220
  statusCode: status,
210
221
  body: content,
@@ -232,6 +243,16 @@ var Router = class {
232
243
  }),
233
244
  status: function(code) {
234
245
  return {
246
+ raw: () => res,
247
+ binary: (content, contentType = "application/octet-stream") => ({
248
+ statusCode: code,
249
+ body: content,
250
+ headers: {
251
+ "Content-Type": contentType,
252
+ "Content-Length": content.length.toString()
253
+ },
254
+ isRaw: true
255
+ }),
235
256
  text: (content) => ({
236
257
  statusCode: code,
237
258
  body: content,
@@ -281,11 +302,8 @@ var Router = class {
281
302
  }
282
303
  };
283
304
 
284
- // src/utils/getTruthyValue.ts
285
- function getTruthyValue(value) {
286
- if (value === "true" || value === true) return true;
287
- return false;
288
- }
305
+ // src/config.ts
306
+ var import_mikroconf = require("mikroconf");
289
307
 
290
308
  // src/utils/configDefaults.ts
291
309
  var configDefaults = () => {
@@ -293,6 +311,7 @@ var configDefaults = () => {
293
311
  port: Number(process.env.PORT) || 3e3,
294
312
  host: process.env.HOST || "0.0.0.0",
295
313
  useHttps: false,
314
+ useHttp2: false,
296
315
  sslCert: "",
297
316
  sslKey: "",
298
317
  sslCa: "",
@@ -304,6 +323,45 @@ var configDefaults = () => {
304
323
  allowedDomains: ["*"]
305
324
  };
306
325
  };
326
+ function getTruthyValue(value) {
327
+ if (value === "true" || value === true) return true;
328
+ return false;
329
+ }
330
+
331
+ // src/config.ts
332
+ var defaults = configDefaults();
333
+ var baseConfig = (options) => ({
334
+ configFilePath: "mikroserve.config.json",
335
+ args: process.argv,
336
+ options: [
337
+ { flag: "--port", path: "port", defaultValue: defaults.port },
338
+ { flag: "--host", path: "host", defaultValue: defaults.host },
339
+ { flag: "--https", path: "useHttps", defaultValue: defaults.useHttps, isFlag: true },
340
+ { flag: "--http2", path: "useHttp2", defaultValue: defaults.useHttp2, isFlag: true },
341
+ { flag: "--cert", path: "sslCert", defaultValue: defaults.sslCert },
342
+ { flag: "--key", path: "sslKey", defaultValue: defaults.sslKey },
343
+ { flag: "--ca", path: "sslCa", defaultValue: defaults.sslCa },
344
+ {
345
+ flag: "--ratelimit",
346
+ path: "rateLimit.enabled",
347
+ defaultValue: defaults.rateLimit.enabled,
348
+ isFlag: true
349
+ },
350
+ {
351
+ flag: "--rps",
352
+ path: "rateLimit.requestsPerMinute",
353
+ defaultValue: defaults.rateLimit.requestsPerMinute
354
+ },
355
+ {
356
+ flag: "--allowed",
357
+ path: "allowedDomains",
358
+ defaultValue: defaults.allowedDomains,
359
+ parser: import_mikroconf.parsers.array
360
+ },
361
+ { flag: "--debug", path: "debug", defaultValue: defaults.debug, isFlag: true }
362
+ ],
363
+ config: options
364
+ });
307
365
 
308
366
  // src/MikroServe.ts
309
367
  var MikroServe = class {
@@ -314,42 +372,11 @@ var MikroServe = class {
314
372
  * @description Creates a new MikroServe instance.
315
373
  */
316
374
  constructor(options) {
317
- const defaults = configDefaults();
318
- const config = new import_mikroconf.MikroConf({
319
- configFilePath: "mikroserve.config.json",
320
- args: process.argv,
321
- options: [
322
- { flag: "--port", path: "port", defaultValue: defaults.port },
323
- { flag: "--host", path: "host", defaultValue: defaults.host },
324
- { flag: "--https", path: "useHttps", defaultValue: defaults.useHttps, isFlag: true },
325
- { flag: "--cert", path: "sslCert", defaultValue: defaults.sslCert },
326
- { flag: "--key", path: "sslKey", defaultValue: defaults.sslKey },
327
- { flag: "--ca", path: "sslCa", defaultValue: defaults.sslCa },
328
- {
329
- flag: "--ratelimit",
330
- path: "rateLimit.enabled",
331
- defaultValue: defaults.rateLimit.enabled,
332
- isFlag: true
333
- },
334
- {
335
- flag: "--rps",
336
- path: "rateLimit.requestsPerMinute",
337
- defaultValue: defaults.rateLimit.requestsPerMinute
338
- },
339
- {
340
- flag: "--allowed",
341
- path: "allowedDomains",
342
- defaultValue: defaults.allowedDomains,
343
- parser: import_mikroconf.parsers.array
344
- },
345
- { flag: "--debug", path: "debug", defaultValue: defaults.debug, isFlag: true }
346
- ],
347
- config: options
348
- }).get();
375
+ const config = new import_mikroconf2.MikroConf(baseConfig(options || {})).get();
349
376
  if (config.debug) console.log("Using configuration:", config);
350
377
  this.config = config;
351
378
  this.router = new Router();
352
- const requestsPerMinute = config.rateLimit.requestsPerMinute || defaults.rateLimit.requestsPerMinute;
379
+ const requestsPerMinute = config.rateLimit.requestsPerMinute || configDefaults().rateLimit.requestsPerMinute;
353
380
  this.rateLimiter = new RateLimiter(requestsPerMinute, 60);
354
381
  if (config.rateLimit.enabled === true) this.use(this.rateLimitMiddleware.bind(this));
355
382
  }
@@ -423,7 +450,22 @@ var MikroServe = class {
423
450
  */
424
451
  createServer() {
425
452
  const boundRequestHandler = this.requestHandler.bind(this);
426
- if (this.config.useHttps) {
453
+ if (this.config.useHttp2) {
454
+ if (!this.config.sslCert || !this.config.sslKey)
455
+ throw new Error("SSL certificate and key paths are required when useHttp2 is true");
456
+ try {
457
+ const httpsOptions = {
458
+ key: (0, import_node_fs.readFileSync)(this.config.sslKey),
459
+ cert: (0, import_node_fs.readFileSync)(this.config.sslCert),
460
+ ...this.config.sslCa ? { ca: (0, import_node_fs.readFileSync)(this.config.sslCa) } : {}
461
+ };
462
+ return import_node_http2.default.createSecureServer(httpsOptions, boundRequestHandler);
463
+ } catch (error) {
464
+ if (error.message.includes("key values mismatch"))
465
+ throw new Error(`SSL certificate and key do not match: ${error.message}`);
466
+ throw error;
467
+ }
468
+ } else if (this.config.useHttps) {
427
469
  if (!this.config.sslCert || !this.config.sslKey)
428
470
  throw new Error("SSL certificate and key paths are required when useHttps is true");
429
471
  try {
@@ -477,8 +519,14 @@ var MikroServe = class {
477
519
  this.setSecurityHeaders(res, this.config.useHttps);
478
520
  if (isDebug) console.log(`${method} ${url}`);
479
521
  if (req.method === "OPTIONS") {
480
- res.statusCode = 204;
481
- res.end();
522
+ if (res instanceof import_node_http.default.ServerResponse) {
523
+ res.statusCode = 204;
524
+ res.end();
525
+ } else {
526
+ const h2Res = res;
527
+ h2Res.writeHead(204);
528
+ h2Res.end();
529
+ }
482
530
  return;
483
531
  }
484
532
  try {
@@ -603,14 +651,24 @@ var MikroServe = class {
603
651
  * @description Set security headers.
604
652
  */
605
653
  setSecurityHeaders(res, isHttps = false) {
606
- res.setHeader("X-Content-Type-Options", "nosniff");
607
- res.setHeader("X-Frame-Options", "DENY");
608
- res.setHeader(
609
- "Content-Security-Policy",
610
- "default-src 'self'; script-src 'self'; object-src 'none'"
611
- );
612
- if (isHttps) res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
613
- res.setHeader("X-XSS-Protection", "1; mode=block");
654
+ const securityHeaders = {
655
+ "X-Content-Type-Options": "nosniff",
656
+ "X-Frame-Options": "DENY",
657
+ "Content-Security-Policy": "default-src 'self'; script-src 'self'; object-src 'none'",
658
+ "X-XSS-Protection": "1; mode=block"
659
+ };
660
+ if (isHttps || this.config.useHttp2)
661
+ securityHeaders["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
662
+ if (res instanceof import_node_http.default.ServerResponse) {
663
+ Object.entries(securityHeaders).forEach(([name, value]) => {
664
+ res.setHeader(name, value);
665
+ });
666
+ } else {
667
+ const h2Res = res;
668
+ Object.entries(securityHeaders).forEach(([name, value]) => {
669
+ h2Res.setHeader(name, value);
670
+ });
671
+ }
614
672
  }
615
673
  /**
616
674
  * @description Sends a response with appropriate headers.
@@ -619,10 +677,23 @@ var MikroServe = class {
619
677
  const headers = {
620
678
  ...response.headers || {}
621
679
  };
622
- res.writeHead(response.statusCode, headers);
623
- if (response.body === null || response.body === void 0) res.end();
624
- else if (typeof response.body === "string") res.end(response.body);
625
- else res.end(JSON.stringify(response.body));
680
+ const hasWriteHead = (res2) => {
681
+ return typeof res2.writeHead === "function" && typeof res2.end === "function";
682
+ };
683
+ if (hasWriteHead(res)) {
684
+ res.writeHead(response.statusCode, headers);
685
+ if (response.body === null || response.body === void 0) res.end();
686
+ else if (response.isRaw) res.end(response.body);
687
+ else if (typeof response.body === "string") res.end(response.body);
688
+ else res.end(JSON.stringify(response.body));
689
+ } else {
690
+ console.warn("Unexpected response object type without writeHead/end methods");
691
+ res.writeHead?.(response.statusCode, headers);
692
+ if (response.body === null || response.body === void 0) res.end?.();
693
+ else if (response.isRaw) res.end?.(response.body);
694
+ else if (typeof response.body === "string") res.end?.(response.body);
695
+ else res.end?.(JSON.stringify(response.body));
696
+ }
626
697
  }
627
698
  /**
628
699
  * @description Sets up graceful shutdown handlers for a server.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MikroServe
3
- } from "./chunk-E3RGQ7QF.mjs";
3
+ } from "./chunk-SLBFEKEH.mjs";
4
4
  import "./chunk-ZFBBESGU.mjs";
5
- import "./chunk-KJT4SET2.mjs";
6
- import "./chunk-NSHBEU32.mjs";
7
- import "./chunk-6HESV5Q6.mjs";
5
+ import "./chunk-GUYBTPZH.mjs";
6
+ import "./chunk-YOHL3T54.mjs";
7
+ import "./chunk-JJX5XRNB.mjs";
8
8
  export {
9
9
  MikroServe
10
10
  };
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
  */
package/lib/Router.js CHANGED
@@ -143,6 +143,16 @@ var Router = class {
143
143
  path,
144
144
  state: {},
145
145
  // Add the missing state property
146
+ raw: () => res,
147
+ binary: (content, contentType = "application/octet-stream", status = 200) => ({
148
+ statusCode: status,
149
+ body: content,
150
+ headers: {
151
+ "Content-Type": contentType,
152
+ "Content-Length": content.length.toString()
153
+ },
154
+ isRaw: true
155
+ }),
146
156
  text: (content, status = 200) => ({
147
157
  statusCode: status,
148
158
  body: content,
@@ -170,6 +180,16 @@ var Router = class {
170
180
  }),
171
181
  status: function(code) {
172
182
  return {
183
+ raw: () => res,
184
+ binary: (content, contentType = "application/octet-stream") => ({
185
+ statusCode: code,
186
+ body: content,
187
+ headers: {
188
+ "Content-Type": contentType,
189
+ "Content-Length": content.length.toString()
190
+ },
191
+ isRaw: true
192
+ }),
173
193
  text: (content) => ({
174
194
  statusCode: code,
175
195
  body: content,
package/lib/Router.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Router
3
- } from "./chunk-KJT4SET2.mjs";
3
+ } from "./chunk-GUYBTPZH.mjs";
4
4
  export {
5
5
  Router
6
6
  };
@@ -119,6 +119,16 @@ var Router = class {
119
119
  path,
120
120
  state: {},
121
121
  // Add the missing state property
122
+ raw: () => res,
123
+ binary: (content, contentType = "application/octet-stream", status = 200) => ({
124
+ statusCode: status,
125
+ body: content,
126
+ headers: {
127
+ "Content-Type": contentType,
128
+ "Content-Length": content.length.toString()
129
+ },
130
+ isRaw: true
131
+ }),
122
132
  text: (content, status = 200) => ({
123
133
  statusCode: status,
124
134
  body: content,
@@ -146,6 +156,16 @@ var Router = class {
146
156
  }),
147
157
  status: function(code) {
148
158
  return {
159
+ raw: () => res,
160
+ binary: (content, contentType = "application/octet-stream") => ({
161
+ statusCode: code,
162
+ body: content,
163
+ headers: {
164
+ "Content-Type": contentType,
165
+ "Content-Length": content.length.toString()
166
+ },
167
+ isRaw: true
168
+ }),
149
169
  text: (content) => ({
150
170
  statusCode: code,
151
171
  body: content,
@@ -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,